Java Springの逆引きメモ -14ページ目

Java Springの逆引きメモ

JavaのSpring frameworkのメモを書いていきます!
初心者の勉強ノートなので間違いがあるかもしれませんが、何かヒントになることがあれば幸いです。

'2012.03.01 追記:SimpleJdbcTemplate はSpring3.1から廃止(depricated)になったようです。

   ですので、JdbcTemplateかNamedParameterJdbcTemplateを使用した方が良いと思います。



ここでは、SpringJDBCを使用して、DBから値を取得するサンプルを記述します。

SimpleJdbcTemplate クラスは、以下のような場合に良く使用します。


 ・SQL文にプレースするものがない場合

 ・SQL文にプレースがあるが?を使用したい場合


さて、早速サンプルをみてみましょう。


【DaoインターフェースのJavaコード例】

import java.util.List;
import business.domain.Member;
public interface MemberDao { /** * 会員検索をする。 * @return 検索結果 */ List<Member> findMember();

}

【DaoのJavaコード例】

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.validator.GenericValidator;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;

import business.domain.Member;


public class MemberDaoImpl extends JdbcDaoSupport implements MemberDao {

  //会員情報マッピング
  protected class MemberRowMapper 
  implements ParameterizedRowMapper<Member> {
    
    public Member mapRow(ResultSet rs, int rowNum)
    throws SQLException {
        Member mem = new Member();
        mem.setId(rs.getString("id"));
        mem.setKana(rs.getString("kana"));
        mem.setKanji(rs.getString("kanji"));
      return mem;
    }
  }

  //JDBCのテンプレート
  protected NamedParameterJdbcTemplate namedParameterTemplate;
  protected SimpleJdbcTemplate simpleTemplate;
  //初期化処理
  protected void initDao()
  {
    this.namedParameterTemplate = 
                   new NamedParameterJdbcTemplate(getDataSource());
    this.simpleTemplate = new SimpleJdbcTemplate(getDataSource());
  }
  
  //  
  @SuppressWarnings("unchecked")
  public List<Member> findMember() {
    //
    String sql = "select * from t_member ";
    
    //会員リスト
    List<Member> memberList = this.simpleTemplate.query(
      sql, new MemberRowMapper()
    ); 
    
    return memberList;
  }

}



【Springの設定】

  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.postgresql.Driver"/>
    <property name="url">
      <value>jdbc:postgresql://localhost/test?useUnicode=true&amp;characterEncoding=utf-8</value>
    </property>
    <property name="username" value="user" />
    <property name="password" value="pass" />
</bean>
  <bean id="memberDao" class="dao.MemberDaoImpl">
    <property name="dataSource" ref="dataSource" />
  </bean>


【説明】

Javaのクラスは、JdbcDaoSupport を継承してメソッドを実装していきます。

まず準備として、MemberRowMapperのようなマッピングクラスを作成します。

これは、ParameterizedRowMapperを継承して作成します。

実装するのはmapRowメソッドだけで、この中でResultSetとオブジェクトのマッピングをします。


次にするのは、実際のSQLの処理です。

といっても、SQL文を作ってsimpleTemplate.query( )を呼ぶだけです。


Springの設定ファイルでは、データソースと先ほど作成したDaoのクラスを設定するだけです。


これでDBから値を取得できます。



【?のプレースを使用する場合の例】

queryのところが以下のようになります。


String sql = "select * from t_member where date < ?";


//会員リスト
List<Member> memberList = this.simpleTemplate.query(
sql, new MemberRowMapper(), new Date()
);

引数の最後にプレースするオブジェクトを指定します。

ここは可変引数になっているので、?の数だけ指定していきます。



【補足】

Springが用意しているSQLの実行をするクラスは以下の2つです。


//JDBCのテンプレート
protected NamedParameterJdbcTemplate namedParameterTemplate;
protected SimpleJdbcTemplate simpleTemplate;

それぞれselectも更新系も実行するメソッドがそれぞれ用意されています。

2つの違いは何でしょうか?


SimpleJdbcTemplate はプレースホルダーに"?"を使用します。

ですので、置き換えするデータを可変引数で指定するか、Object配列で指定します。

NamedParameterJdbcTemplate は、プレースホルダーに":id"のようなパラメタ名を

使用できます。


単純なものは上の方が便利ですし、プレースホルダを使用する場合は下の方が便利です。

うまく使い分けて行ってください。



【その他】

上の2つにはqueryForIntなどの便利なメソッドもあります。

これはマッピングクラスを引数に取る必要がないのです。


select count(*) from member


のような場合、マッピングクラスをわざわざ用意するのは大変です。

こんなときに使用します。


他にも便利なメソッドがあるので使っていきましょう!



参考:

・トップ

DIの設定ファイルを書くには?

SpringJDBCの機能について

・SpringJDBCで作成したDaoとビジネスロジックを連携するには?

・DBからデータを取得するには?(SimpleJdbcTemplate のサンプル)

・DBからデータを取得するには?(NamedParameterJdbcTemplateのサンプル)

・NamedParameterJdbcTemplateをうまく使うには? (HashMap版)

・結果をMapの配列で受け取るには?

・DBにデータを登録するには?

・DBへのデータ登録を簡単にするには?

・実行したSQL文のログを出力するには?



ここでは、前の記事 に書いたDelegatingActionProxy系での連携方法の実際を見てみます。

4つのうちこれを選んだのは「結局どれにしたらいいの?」で書きましたとおりです。


以下、サンプルなので一部省略するかもしれませんが、書いてみます。



【Actionクラスサンプル】

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class TestAction extends Action {

 @Override
 public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    //
    //String value = mapping.getParameter();
    
    test();
    return mapping.getInputForward();
 }
  
}


【Struts設定ファイルの記述例】


  <action scope="request" input="/WEB-INF/jsp/HelloWorld1.jsp" 
  type="org.springframework.web.struts.DelegatingActionProxy" 
  path="/test" />




【Spring設定ファイルの記述例】

 <bean name="/test" class="presentation.action.TestAction" />




【説明】

連携で必要なのは、

 ①普通どおりにStrutsのActionの継承クラスを作成すること

 ②Struts設定ファイルにactionタグを登録。このとき、typeは

  org.springframework.web.struts.DelegatingActionProxy

  にする。

 ③Spring設定ファイルに①で作成したクラスを設定する。

  このとき、Strutsのパス(この場合、/test)とid名をあわせる。


これだけです。

簡単でしょ?



<動作について>

動作は以下のようになります。

①クライアントからのリクエストをStrutsが受け取る

②Strutsは設定ファイルに従ってtypeに設定されたクラスを取得する。

 具体的にはDelegatingActionProxyクラスが設定されているのでそれに処理を渡す。

DelegatingActionProxyは、内部でStringの設定を呼び出して、リクエストのパスと同じidのbeanを検索し、

 取得する。

④取得したbeanのexecute()メソッドを呼び出す。




参考:

Strutsと連携するには? (理論)




StrutsとSpringを連携するには、一般に4つの方法があります。

4つにはそれぞれ欠点があるので、システムの性格に応じて方法を選んでいく必要があります。

そして、自分が考える第5の方法があるので、それはあとの記事 で見ることにします。



名称 概要 Spring設定ファイルへの記述 制限がかかる箇所
ActionSupport系を使う

Springが提供するActionSupportクラスを継承させる方法です。ActionSupportクラス自体はStrutsのActionクラスを継承しています。

なし

Springの設定ファイルにActionクラスが記述できない(DIが使えない)。つまり、Actionに対してAOPなどを使用できない。

DelegatingActionProxy系を使う

Springが提供するDelegatingActionProxyクラスをStrutsのActionとして使用する。

必要 ※問題点1
DelegatingRequestProcessor系を使う Strutsの設定ファイルに左記のクラスを設定することでRequestProcessorを乗っ取ることで連携します。 必要

※問題点1

と、StrutsではRequestProcessorは1つしか設定できない問題あり。

それをSpringが使ってしまうので、他の人がReqestProcessorの機能拡張をしたいときにできない。

AutowiringRequestProcessor系を使う

Strutsの設定ファイルに左記のクラスを設定することでReqestProcessorを乗っ取ることで連携します。

ただし、Springファイルにパスを記述する必要がないため、※問題点1のようなことが発生しません。

なし

DIが使えない。

AOPが使えない。


これらの実際のやり方は、以下のページが詳しいです。

 http://itpro.nikkeibp.co.jp/article/COLUMN/20080929/315624/

メリット、デメリットのまとめ(下のほうに表があります)

 http://itpro.nikkeibp.co.jp/article/COLUMN/20080929/315624/?ST=develop&P=5


それぞれの問題点:

 http://d.hatena.ne.jp/chiba_mk3/20080701/1215187585

 


※問題点1について
 1.Strutsのactionに設定するpathと同じidでSpring設定ファイルにActionクラスを設定しないといけません。

  ですので、1つパスが増えると2つのファイルに記述をしないといけないという問題があります。

 2.Springであれば、beanの使い回しができるのが当たり前ですが、beanのidをパス名と合わせない

  といけないです。ですので、同じActionクラスでも、path名が違えば、違うbeanとして設定しないといけません。 

 3.もうひとつ、beanのid名を自由に選べないという弱点もあります。

 

以下の感じでファイルに2つの記述が必要です。


Spring設定ファイル:

<bean name="/login" class="Aaaa"/>

<bean name="/topmenu" class="Aaaa"/>←classが同じなので本当は上だけ記述して使いまわしたいけどできない


Struts設定ファイル:

<action path="/login" ...../>

<action path="/topmenu" ...../>


※この問題の逃げ道はあります。(参照: 連携方法を拡張して、より自由度が高い連携をするには?



※「DIが使えない」問題点について

これはどういう意味かというと、StrutsのActionクラスをSpringの設定ファイルに設定できないという意味です。

これによる問題点は、Actionクラスにsetterを作成しても、Springの設定ファイル上で設定ができないということです。

もっと言うと、Springを使用できれば何かあってもsetterで設定するクラスや定数を変更することで動作を変更できます。これが全くできないということになります。

また、Springで設定されていないので、当然ながらAOPも使用できないということにもなります。

・DIxAOPの機能について の注意点を参照。


【結局どれがいいの?】

正直どれも何かしらの面倒くささはあります。

しかし、面倒なことよりも、後で機能が使えない制限の方が問題です。

例えばDIを使えないとか、AOPを使えないとか、ReqestProcessorを拡張できないとかです。

すると必然的に以下の物が残ります。


  ・DelegatingActionProxy


個人的には、これがいいと思っています。

が、ケースバイケースなので、システムごとに考えなきゃですかね。




参照:

・トップ

Strutsと連携するには?(理論編)

Strutsと連携するには?(実践編)

連携方法を拡張して、より自由度が高い連携をするには?

検索画面を簡単に実現するには?

SpringのControler内で指定したbeanを取得するにはどうしたらいいでしょうか。



【Springの派生クラスをextendsしている場合】

実はそれ程難しくないんです!

Springが用意しているControllerの派生クラス(SimpleFormControllerなど)を継承している場合、以下のメソッドがあります。


getApplicationContext()


これで取得できます。


使用例:

(MemberService)getApplicationContext().getBean("memberService");




【SpringMVCのControllerの場合】

SpringのControllerインターフェースを直接implementsしている場合。

以下のようにします。


SpringのWebContentGeneratorクラスをextendsします。

すると、getApplicationContext()を使用できます。




【StrutsのDelegatingActionProxyの派生クラス内でbeanを取得したい場合】

この場合は、以下のように取得します。


使用例:

(MemberService)getWebApplicationContext(getServlet(), mapping.getModuleConfig()).getBean("memberService");



【上記以外 (完全にSpringと関係ないメソッドなどで呼び出したい場合、StrutsのAction)】

この場合、Springと関係する箇所から引数としてApplicationContextを引き回した方がいいかもしれません。

でも、一応、そのまま取得する方法を書いておきますね。

WebApplicationContextUtilsを使用します。

ServletContextが必要なので、どこかで適当に取得してから、使用します。


使用例:

ServletContext context = request.getSession().getServletContext();


WebApplicationContext wac = WebApplicationContextUtils
.getRequiredWebApplicationContext(context);


(MemberService)wac.getBean("memberService");


どうです?

うまく取得できました?



参照:

・トップ

・SpringMVCの機能について

・Controller内でApplicationContextを取得するには?

・リクエストの文字コードを設定ファイルで制御するには?

・SpringMVCを使用してWEBを作るには? (基礎編)

・Validatorでパラメタの妥当性チェックをするには?

・SpringMVCを使用してWEBを作るには? (実践編) : (パラメタとオブジェクトのバインドの方法)

・tilesについて


ここでは、SpringJDBCとはどんな機能かを簡単に見ていこうと思います。


SpringJDBCは、JavaのオブジェクトとDBのテーブルの間を仲立ちする機能です。

一般的にはO/Rマッピングといいます。

総称を言ったところで何もわからないのでもう少し具体的に行きましょー目




【使用例】

少し準備のコードが必要ですが、ここでは準備を無視して使用例だけ見てみましょう。


<取得する情報>

会員情報クラス(Member)を取得することを考えます。



<①複数の結果を取得する場合の例>

String sql = "select * from member ";


List<Member> memberList = this.simpleTemplate.query(
sql, new MemberRowMapper()
);



<②1レコードの結果を取得する場合の例>

String sql = "select * from member where id=:id";

Map key = new HashMap();

key.put("id", "id0006");


Member member = this.namedParameterTemplate.query(
sql, key, new MemberRowMapper()
);



【説明】

①も②も同じMemberRowMapper(自作クラス)を引数にしています。

つまり、再利用しています。


こんな感じで、ResultSetからMemberオブジェクトへのマッピングが簡単になり、

1つマッピングクラスを作れば、Listも1つのオブジェクトも取得できるのです。

しかもコードの可読性が高くなっています。



もうひとつの注目点は、②の:idの部分。

そのあと、key.put()でidの値を設定しています。

なんとなく想像つくと思いますが、この値でSQL文が置き換わるのです。

普通、jdbcでは?を置き換えできますが、数が多くなるとわけが分からなくなります。

それを解決してくれます。


結局、②のSQL文は以下のようになります。

select * from member where id='id0006'



地味な機能ですが、結構便利ですよ!



【機能で重要なこと】

SpringJDBCは地味な感じですが、裏でいろいろやっています。

そのうちのひとつについて、書いておきます。

実は、データソースをプールしているんです。

しかも、スレッドごとに1つデータソースを割り当ててくれます。

ですので、何も気にせずにSpringJDBCからデータソースを取得しても、

同じスレッドなら同じデータソースにアクセスできます。


もしこれをSpringJDBCを使用せずに行うと、トランザクションの最初にデータソースを取得して、

メソッドを呼び出すたびに引数に入れて引き回さないといけません。

そうしないと、トランザクションなどを制御できないからです。


このあたりは見落としがちなのですが、真面目にやろうとするとかなりコードのルールを作らないといけません。

1人ならいいのですが、複数で開発するときなどは大変です。

ぜひともSpringJDBCを使って、効率化しましょう!



【トランザクションについて】

さらにうれしい機能が!音譜

トランザクションの管理をSpringの設定ファイルだけでできます!

どんな感じかというと、例外が発生したときにrollbackし、例外がなければcommitするんです。

これは指定のメソッドに対して、メソッドの最初でトランザクション開始し、メソッドを抜けるときにトランザクションを終了します。(AOPの機能なので、以下のようにAOPの設定の仕方と同じになります)


 メソッド指定例: execution(* business.service..*(..))


もうひとつ!音譜

では、指定のメソッドがさらにその中で、Springで設定されたメソッドを呼んだ場合どうなるんでしょう?

例えば、以下のようなケースです。


Springでのトランザクション: AメソッドとBメソッドに対して設定。

  void A(){
    B();
    ...
  }

このような場合、一番外側のメソッドに対してだけトランザクションが実行されるんです

上記の場合、A( )の開始でトランザクションが開始されて、A( )を抜けるときにトランザクションを終了します。

B( )のメソッド内部では、トランザクションの開始はしません!

SpringJDBCを使用しないと、A、Bのメソッド内それぞれでトランザクションの開始とrollbackを記述しないといけませんよね?これって大変です。

しかも、A、Bのメソッドは再利用の観点から言うと、それぞれ独立でも使用したいし、上の例のように内部からさらに呼び出したいケースがあると思います。

そうすると、独立で使用するのか?内部から呼び出すのかのフラグを引数に持たないと実現できません。

このケースに対応するにはSpringのトランザクションを使用しないと難しいですよね?


==このトランザクションの機能はかなり有用です!!

Oracleのようにトランザクションの入れ子ができると、ある程度上記の入れ子でメソッドが呼ばれる場合も

対処できます。

しかし、Postgresはトランザクションの入れ子ができません!!

そういうDBもあるのでそういう意味でもかなり有用な機能です!



【try~catchについて】

普通、JDBC絡みの処理を書くと、try~catchを記述しないといけません。

なぜならJDBC関連のメソッドがキャッチしないといけない例外をthrowするからです。

意外とこれは面倒です。

しかしSpringJDBCを使用すると、例外をすべてRuntimeExceptionに変換してくれます!

つまり、try~catchをDao内で書く必要がなくなります!

これは可読性が上がるとともに、記述が楽になります。



【補足】

Hibernateは有名なO/Rマッピングで、かなり機能が多いし強力です。

SQL文を書かずに、設定ファイルだけでDBとの連携ができるのは魅力です。

いい機能だと思います。

しかし、DBの設計にやたらと口を出してきます。

例えば、必ず一意のIDをテーブルに持たせないといけない。それはprimaryキーにしないといけない。

など。

しかも、Hibernateを使用すると、勝手にDBのindexなどを変えてしまうのです。

また、複雑にデータを取る場合、SQL文を自分で書くこともできるのですが

ちょっと複雑になるとすぐに、「設定ファイルだけでは対応できませーん汗」って、悲鳴を上げるので、大きなシステムを扱う場合、結局SQL文を書かないといけなくなります。

正直、趣味の問題ですが、自分はあまりHibernateの考え方が得意ではありません。



それよりも、シンプルですが、SQL文が見え、コードが分かりやすい、SpringのJDBCの方が好きです。

一度、勉強してみる価値はあると思います!


是非、使ってみましょうラブラブ




参考:

・SpringJDBCで作成したDaoとビジネスロジックを連携するには?

・DBからデータを取得するには?(SimpleJdbcTemplate のサンプル)

・DBからデータを取得するには?(NamedParameterJdbcTemplateのサンプル)

・NamedParameterJdbcTemplateをうまく使うには?

・結果をMapの配列で受け取るには?

・DBにデータを登録するには?

・DBへのデータ登録を簡単にするには?

・実行したSQL文のログを出力するには?

・DIxAOPの機能について