Spring 2.0によるDBアプリケーションの作成

第2回 Spring JDBC

最終更新日:2007/06/11

目次

JDBCを使ったデータアクセス

Spring 2.0では大きく分けて次の2つのデータアクセスがサポートされています。

このうちJDBCでは、SpringがJDBCを抽象化したフレームワークを提供しており、JDBC APIを直接使うよりも簡潔にデータアクセスコードを記述できるようになっています。具体的にはコネクションの取得/解放、PreparedStatementやResultSetの隠蔽などを行ってくれます。

JDBCサポートはSpring 1.xからありましたが、Spring 2.0で追加されたJava 5対応クラス(SimpleJDBCTemlateなど)を使うと更に記述が簡単になりました。今回はSpringのJDBCサポート機能を紹介しながら、データアクセスを行うクラスを作成していきます。

JdbcTemplate

JdbcTemplateとは?

JdbcTemplateはSpring JDBCサポートのコアクラスです。JDBC APIではデータソースからコネクションの取得、PreparedStatementの作成、ResultSetの解析、コネクションの解放などを行う必要がありますが、JdbcTemplateを使うことでこれらの処理の多くが隠蔽され、より簡単にデータアクセスを行うことができます。

以下はJdbcTemplateによるデータアクセスの例です。まず最初はテーブルの1行をJavaBean(Bumonクラス)に格納するSelect文です。

Select

public Bumon load(String cdBumon) {
  return (Bumon) jdbcTemplate.queryForObject("SELECT * FROM BUMON WHERE CD_BUMON = ?",
    new Object[] { cdBumon }, new BumonRowMapper());
}

static class BumonRowMapper implements RowMapper {
  public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
    Bumon bumon = new Bumon();
    bumon.setCdBumon(rs.getString("CD_BUMON"));
    bumon.setNmBumon(rs.getString("NM_BUMON"));
    return bumon;
  }
}

queryForObjectというメソッドで引数のSQLを発行します。SQL文中の'?'はプレースホルダで、queryForObjectの第2引数で与えられるオブジェクト配列の要素が割り当てられます。RowMapperはテーブルの列の値とJavaBeanのフィールドを関連付けるマッパー(インタフェース)です。

次はUpdate文です。Selectの場合と同様なやり方でクエリーを発行します。プレースホルダの使い方も同じです。こちらはプレースホルダが2つあるので、最初の'?'にオブジェクト配列の最初の要素が割り当てられ、2番目の'?'に2番目の要素が割り当てられます。

Update

public void update(Bumon bumon) {
  jdbcTemplate.update("UPDATE BUMON SET NM_BUMON = ? WHERE CD_BUMON = ?",
    new Object[] { bumon.getCdBumon(), bumon.getNmBumon() });
}

このようにJdbcTemplateに用意されているqueryXXXメソッドやupdateメソッドなどを使うことによって、簡単にデータアクセスができることがわかります。通常のJDBCプログラミングではコネクションをデータソースから取得したり、finally節でコネクションの解放を行ったりとなにかと面倒でしたが、そういったコードは必要ありません。またJdbcTemplateではSQL文を直接記述するため、ORマッピングに比べて発行されるSQLを把握できることや、問題が発生した場合に手を打ちやすい(SQLを直接変更できるため)というメリットもあると思います。

このJdbcTemplateをラップし、もう少し簡単にJdbcTemplateを使うことができるクラスがSpring 2.0で追加されました。次は追加された2つのクラスを見ていきます。

NamedParameterJdbcTemplate

SQLのプレースホルダ('?')を名前付きパラメータ(:変数名)に置き換えることができるよう拡張されたクラスです。例えば上で挙げた update 文にはプレースホルダが2つあったため、プレースホルダにあてはめる値の順番を注意する必要がありますが、その順番を間違える可能性があります。プレースホルダに名前を付け、同じ名前のキーを持つMapやSqlParameterSource(のサブクラス)をバインド変数として与えることで、バインド時のミスを少なくすることができるクラスです。

上記 Update 文をNamedParameterJdbcTemplateを使って置き換えると以下のようになります。

Update1

public void insert(Bumon bumon) {
  Map params = new HashMap();
  params.put("cdBumon", bumon.getCdBumon());
  params.put("nmBumon", bumon.getNmBumon());
  jdbcTemplate.update("INSERT INTO BUMON (CD_BUMON, NM_BUMON) VALUE (:cdBumon, :nmBumon)", params);
}

この例ではMapを作る必要があるため少し面倒です。別の方法として、名前付きパラメータをBeanのプロパティ名に合わせることにより、Mapの生成を省略することができます。

Update2

public void insert(Bumon bumon) {
  jdbcTemplate.update("INSERT INTO BUMON (CD_BUMON, NM_BUMON) VALUE (:cdBumon, :nmBumon)", new BeanPropertySqlParameterSource(bumon));
}

BumonクラスにはcdBumon, nmBumonフィールドとそのGetter/Setterが定義されています。この場合、BeanPropertySqlParameterSourceクラスを使うと、Beanのプロパティ名に対応する名前付きパラメータにマッピングしてくれます。

Selectの場合もやはりMapを使ってパラメータを構築します。BumonRowMapperクラスはJdbcTemplateを使う場合と同じなので省略します。

Select

public Bumon load(String cdBumon) {
  Map params = new HashMap();
  params.put("cdBumon", cdBumon);
  return (Bumon) jdbcTemplate.queryForObject("SELECT * FROM BUMON WHERE CD_BUMON = :cdBumon", params, new BumonRowMapper());
}

SimpleJdbcTemplate

SimpleJdbcTemplateはJdbcTemplateをJava 5の新機能(Generics, 可変長引数, AutoBoxing)を使って、より簡潔に記述できるよう拡張したクラスです。よってこのクラスを使う場合はJava 5以上が必須になります。

JdbcTemplateではプレースホルダに割り当てる値を、引数の数に関わらずオブジェクト配列(Object[])にする必要があったり、RowMapperを使ったときにキャストが必要でした。これをSimpleJdbcTemplateを使うと以下のようになります。

Select

public Bumon load(String cdBumon) {
  return jdbcTemplate.queryForObject("SELECT * FROM BUMON WHERE CD_BUMON = ?",
    new ParameterizedBumonRowMapper(), cdBumon);
}

static class ParameterizedBumonRowMapper implements ParameterizedRowMapper<Bumon> {
  public Bumon mapRow(ResultSet rs, int rowNum) throws SQLException {
    Bumon bumon = new Bumon();
    bumon.setCdBumon(rs.getString("CD_BUMON"));
    bumon.setNmBumon(rs.getString("NM_BUMON"));
    return bumon;
  }
}

JdbcTemplateの場合と比べて、queryForObject()メソッドの戻り値のキャストが無くなり、プレースホルダに割り当てる値を直接並べられるようになりました。またRowMapperの代わりにParameterizedRowMapperを実装し、更に戻り値の型を記述しています(implements ParameterizedRowMapper<Bumon>)。

以下はUpdateのコードです。2つのプレースホルダに対して、Java 5の可変長引数を使って単にパラメータをメソッドの引数として並べています。

Update

public void update(Bumon bumon) {
  jdbcTemplate.update("UPDATE BUMON SET NM_BUMON = ? WHERE CD_BUMON = ?",
    bumon.getNmBumon(), bumon.getCdBumon());
}

JdbcTemplateの使い分け

SimpleJdbcTemplateはJdbcTemplateのJava 5対応版、NamedParameterJdbcTemplateはプレースホルダを名前付き変数に変更というように、それぞれ目的が違います。JavaのバージョンがJava 5以上で簡易な記述を行いたい場合はSimpleJdbcTemplate, 名前付きパラメータの恩栄を受けたい場合はNamedParameterJdbcTemplateというように、目的に応じて選択すると良いでしょう。

個人的にはSimpleJdbcTemplateがシンプルで使いやすいと思います。

JdbcDaoSupport

JdbcDaoSupportとは?

上で挙げたソースコードでは何の説明もなくjdbcTemplateという変数を使っていますが、これはどこから取得するのでしょうか?またデータソースの設定はどこで行うのでしょうか?

SpringではDAOの作成をサポートするクラスが用意されており、このサポートクラスを使うことにより、JdbcTemplateの取得やデータソースの設定を簡単に行うことができます。

そのためデータアクセスの方法としてJdbcTemplate(及びその派生クラス)を使用する場合は、使用するJdbcTemplateの種類に対応したDAOサポートクラスを使用する方法が一般的でしょう。例えばSimpleJdbcTemplateを使う場合は、SimpleJdbcDaoSupportクラスを継承してDAOクラスを作成するという具合です。JDBCに特化したDAOサポートクラスには以下のクラスが用意されています。

JdbcTemplateの取得

各DAOサポートクラスにはJdbcTemplateを取得するためのGetterメソッドが用意されており、各DAOでそのメソッドを呼び出せば取得できます。例えばSimpleJdbcDaoSupportの場合は、getSimpleJdbcTemplate()メソッドでSimpleJdbcTemplateを取得できます。

データソースの設定

データソースを設定する方法として、Bean定義ファイルにデータソースの設定を記述する方法と、既存のJNDIリソースをBean定義ファイルから参照する方法があります。ここではBean定義ファイルにデータソースの設定を記述する方法を紹介します。

Bean定義ファイルにデータソースの設定を記述する場合は、以下のような<bean>要素を作成します。

<bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
  <property name="driverClassName" value="${db.driver}" />
  <property name="url" value="${db.url}" />
  <property name="username" value="${db.username}" />
  <property name="password" value="${db.password}" />
</bean>

接続するデータベースのURLやJDBCドライバクラス等をプロパティとして記述します。id属性の値は何でも構いませんが、ここではわかりやすく"dataSource"としています。上の例ではドライバクラス(driverClassName)やユーザ名(username)といったDB接続に必要な情報を${db.driver}, ${db.username}のように書いています。直接値を与えても良いですが、データベースに関する情報は環境によって必ず書き換える必要があります。Bean定義ファイルを誤って変更してしまうリスクを回避するために、データベースの情報は別ファイルに分けて管理した方が良いでしょう。これらの情報をdatabase.propertiesというファイルにプロパティファイル形式で記述し、

db.driver=com.mysql.jdbc.Driver
db.url=jdbc:MySQL://localhost:3306/sample?useUnicode=true&characterEncoding=utf8
db.username=root
db.password=root

このdatabase.propertiesというファイルを読み込ませるために、Bean定義ファイルに以下の<bean>要素を追加します。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location" value="database.properties" />
</bean>

こうすると、Bean定義ファイルが読み込まれるときにクラスパス内のdatabase.propertiesファイルも読み込まれ、${xxx}という部分にプロパティファイル内の該当するプロパティの値が埋め込まれることになります。database.propertiesもBean定義ファイルと同じくEclipseのsrcフォルダに配置しておくと良いでしょう。尚、プロパティファイルの名前は上のようにlocationプロパティの値で指定するため、database.propoerties以外の名前でも構いません。

DAOの実装

それではDAOサポートクラスを継承して、DAOを実装していきます。NamedParameterJdbcDaoSupportクラスを継承する場合と、SimpleJdbcDaoSupportを継承する場合の2つを見ていきます。

NamedParameterJdbcDaoSupportを継承する場合

SimpleJdbcDaoSupportを継承したクラスとクラス名を区別するため、BumonDao2としています。import文などは省略。各種SQL文でプレースホルダ('?')の代わりに名前付きパラメータを用い、MapやBeanPropertySqlParameterSourceで値のバインドを行っています。

public class BumonDao2 extends NamedParameterJdbcDaoSupport
  implements IBumonDao {

    /** INSERT */
    public static final String INSERT = 
    "INSERT INTO BUMON (CD_BUMON, NM_BUMON) VALUE (:cdBumon, :nmBumon)";

    /** SELECT */
    public static final String SELECT = 
    "SELECT * FROM BUMON WHERE CD_BUMON = :cdBumon";

    /** UPDATE */
    public static final String UPDATE = 
    "UPDATE BUMON SET NM_BUMON = :nmBumon WHERE cdBumon = :cdBumon";

    /** DELETE */
    public static final String DELETE = 
    "DELETE FROM BUMON WHERE CD_BUMON = :cdBumon";

    /** FIND ALL */
    public static final String FIND_ALL = "SELECT * FROM BUMON";

    public void insert(Bumon bumon) {
        getNamedParameterJdbcTemplate().update(INSERT,
                new BeanPropertySqlParameterSource(bumon));
    }

    public Bumon load(String cdBumon) {
        Map params = new HashMap();
        params.put("cdBumon", cdBumon);
        return (Bumon) getNamedParameterJdbcTemplate().queryForObject(SELECT,
                params, new BumonRowMapper());
    }

    public void update(Bumon bumon) {
        getNamedParameterJdbcTemplate().update(UPDATE,
                new BeanPropertySqlParameterSource(bumon));
    }

    public void delete(String cdBumon) {
        Map params = new HashMap();
        params.put("cdBumon", cdBumon);
        getNamedParameterJdbcTemplate().update(DELETE, params);
    }

    public List<Bumon> findAll() {
        return getNamedParameterJdbcTemplate().query(FIND_ALL, new HashMap(),
                new BumonRowMapper());
    }

    static class BumonRowMapper implements RowMapper {
        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
            Bumon bumon = new Bumon();
            bumon.setCdBumon(rs.getString("CD_BUMON"));
            bumon.setNmBumon(rs.getString("NM_BUMON"));
            return bumon;
        }
    }
}

SimpleJdbcDaoSupportを継承する場合

次にSimpleJdbcDaoSupportを継承する場合です。NamedParameterJdbcDaoSupportを継承する場合とほとんど同じですが、プレースホルダへの値バインドが簡易に行える分だけシンプルになっています。

public class BumonDao extends SimpleJdbcDaoSupport implements IBumonDao {

    /** INSERT */
    public static final String INSERT = 
    "INSERT INTO BUMON (CD_BUMON, NM_BUMON) VALUE (?, ?)";

    /** SELECT */
    public static final String SELECT = 
    "SELECT * FROM BUMON WHERE CD_BUMON = ?";

    /** UPDATE */
    public static final String UPDATE = 
    "UPDATE BUMON SET NM_BUMON = ? WHERE CD_BUMON = ?";

    /** DELETE */
    public static final String DELETE = 
    "DELETE FROM BUMON WHERE CD_BUMON = ?";

    /** FIND ALL */
    public static final String FIND_ALL = "SELECT * FROM BUMON";

    public void insert(Bumon bumon) {
        getSimpleJdbcTemplate().update(INSERT, bumon.getCdBumon(),
                bumon.getNmBumon());
    }

    public Bumon load(String cdBumon) {
        return getSimpleJdbcTemplate().queryForObject(SELECT,
                new ParameterizedBumonRowMapper(), cdBumon);
    }

    public void update(Bumon bumon) {
        getSimpleJdbcTemplate().update(UPDATE, bumon.getNmBumon(),
                bumon.getCdBumon());
    }

    public void delete(String cdBumon) {
        getSimpleJdbcTemplate().update(DELETE, cdBumon);
    }

    public List<Bumon> findAll() {
        return getSimpleJdbcTemplate().query(FIND_ALL,
                new ParameterizedBumonRowMapper());
    }

    static class ParameterizedBumonRowMapper 
    implements ParameterizedRowMapper<Bumon> {
        public Bumon mapRow(ResultSet rs, int rowNum) throws SQLException {
            Bumon bumon = new Bumon();
            bumon.setCdBumon(rs.getString("CD_BUMON"));
            bumon.setNmBumon(rs.getString("NM_BUMON"));
            return bumon;
        }
    }
}

データソースプロパティの追加

DAOクラスは完成しましたが、まだデータソースの設定を行っていないため、このままではDAOクラスがJDBCコネクションを取得できません。DAOがJDBCコネクションの取得を行うことができるようにするため、前回作成したBean定義ファイルのDAO定義に以下のようにdataSourceプロパティを加えます。

<bean id="bumonDao" class="dao.BumonDao">
  <property name="dataSource" ref="dataSource" />
</bean>

<property>要素のname属性の値は必ず"dataSource"とします。各DAOサポートクラスでは setDataSource というメソッドが定義されているため、"dataSource"というプロパティを与えることで、Setterインジェクションが行われるという仕組みです。ref属性では事前に定義したデータソースを表すbean要素のid属性値を与えます。今回の例ではデータソースの設定でデータソースBeanの名前を"dataSource"としたため、ref属性値に"dataSource"を与えています。shainDaoにも同様のプロパティを追加します。

動作確認

ビルドパスの設定

今回作成したプログラムをビルド及び実行するには、以下のJARファイルが必要です。

これらをSpringプロジェクトのビルドパスに追加しておきます。JDBCドライバは接続するデータベースが提供しているものをビルドパスに追加して下さい。

テストケースの作成

単に実行するだけならテストケースを作る必要はないですが、今後も繰り返し動かすことになるため、テストケースを作っておきます。作成するテストケースは今のところ2つだけで、次の項目をテストします。

DBの状態をクリーンに保つため、各テストケースのfinally節でINSERTに対するDELETE等を行っています。

public class BumonServiceTest {

    IBumonService service;

    @Before
    public void setUp() {
        ApplicationContext context = 
      new ClassPathXmlApplicationContext("applicationContext.xml");
        service = (IBumonService) context.getBean("bumonService");
    }

    @After
    public void tearDown() {

    }

    @Test
    public void testRegistAndGetBumon() {
        try {
            Bumon b = new Bumon("000001", "ESC");
            service.registBumon(b);

            // 部門名が正しいか?
            assertEquals("ESC", service.getBumon("000001").getNmBumon());
        } finally {
            service.removeBumon("000001");
        }
    }

    @Test
    public void testGetAllBumon() {
        try {
            Bumon b1 = new Bumon("000001", "ESC");
            Bumon b2 = new Bumon("000002", "OSK");
            service.registBumon(b1);
            service.registBumon(b2);

            // 部門数が2であるか?
            assertEquals(2, service.getAllBumon().size());
        } finally {
            service.removeBumon("000001");
            service.removeBumon("000002");
        }
    }
}

尚、上記テストケースではJUnit4を使用しているため、コンパイル及び実行時にJUnit4のライブラリが必要です。JUnit4の使い方はこちらを参照下さい。

実行

データベースを起動した後、Eclipseからテストケースを実行し、以下のようにグリーンのバーが表示されれば成功です。

今回のまとめ

今回はJdbcTemplateとJdbcDaoSupportクラスを使ったSpring JDBCによるデータアクセスの実装について見てきました。次回はSpringによるトランザクション制御の方法について紹介する予定です。

今回までに作成したファイルはここからダウンロードできます。

第3回 トランザクション管理へ

Spring 2.0 トップへ

資料室へ戻る


Copyright (c) 2007 OKI Software Co., Ltd.