SX(Supplement and eXtension)DbUtils 概要

SX-DbUtilsは、Apacheより提供されている commons-dbutils を、もっと便利に使うユーティリティです。




動作環境および、依存ライブラリ

sx-dbutilsバージョン commons-dbutils バージョン Javaバージョン
sx-dbutils-1.0.x commons-dbutils-1.1 JRE 1.4.2 以上
sx-dbutils-1.0.x-tiger commons-dbutils-1.1 JRE 1.5 以上
sx-dbutils-1.2.x commons-dbutils-1.2 JRE 1.4.2 以上
sx-dbutils-1.2.x-tiger commons-dbutils-1.2 JRE 1.5 以上
sx-dbutils-1.3.x commons-dbutils-1.3 JRE 1.5 以上
  • sx-dbutils-tigerは、sx-dbutils本体に依存しています。



準備

  • サンプルを動かすデーターベースとしては、H2を使用しています。
  • 全てのサンプルは次のようなメソッドで開始するとします。
    1. static void execute(Connection conn) throws Exception{
    2. //TODO ここでサンプルのコードを書く
    3. }



SELECTの結果を処理する

このサンプルでは、次のテーブルを使用します。

  • TABLE01
    1. create table TABLE01(
    2. id IDENTITY PRIMARY KEY ,
    3. name varchar(255),
    4. value varchar(255)
    5. );
    6. insert into TABLE01 (name,value) values('なまえ1','値1');
    7. insert into TABLE01 (name,value) values('なまえ2','値2');
    8. insert into TABLE01 (name,value) values('なまえ3','値3');
    9. insert into TABLE01 (name,value) values('なまえ4','値4');
    10. insert into TABLE01 (name,value) values('なまえ5','値5');
    11. commit;
    idnamevalue
    1なまえ1値1
    2なまえ2値2
    3なまえ3値3
    4なまえ4値4
    5なまえ5値5


最初の1行のみ処理して結果を受け取る

jp.sourceforge.sxdbutils.handlers.SingleHandler

java.sql.ResultSet の最初1行のみ処理したいときは、 SingleHandler を使用します。 SingleHandlerは jp.sourceforge.sxdbutils.SxRowProcessor インターフェースを引数として受け取り、次のように動作します。

  • 結果が返ってきた時にSxRowProcessor のinitメソッドを呼び出す。
  • 最初の1行目をSxRowProcessor のprocessメソッドで処理し、その戻り値をSingleHandlerの戻り値として返す。
  • 結果が1件も返ってこなければ、init/processのどちらも呼ばず、nullを返す。
  • 例:
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. // 結果を1行処理するオブジェクト
    4. SxRowProcessor<String> processor = new SxRowProcessor<String>() {
    5. // 結果が帰ってきたときに1度だけ呼ばれます。
    6. public void init(ResultSetMetaData rsmd) throws SQLException {
    7. System.out.println("結果が帰ってきました。");
    8. }
    9. // ResultSet#nextメソッドがtrueを返すたびに呼ばれます。
    10. public String process(ResultSet rs) throws SQLException {
    11. return String.format("name=%s,
    12. value=%s",
    13. rs.getString("name"),
    14. rs.getString("value"));
    15. }
    16. };
    17. // 1行を受け取るときは、SingleHandlerを使います。
    18. // 最初の1行を処理したら終了するResultSetHandlerです。
    19. ResultSetHandler<String> rsh
    20. = new SingleHandler<String>(processor);
    21. String s = qr.query(conn, "select * from TABLE01", rsh);
    22. System.out.println(s);
    23. }
  • 結果
    結果が帰ってきました。
    name=なまえ1, value=値1
    


全行を処理してList形式で結果を受け取る

jp.sourceforge.sxdbutils.handlers.ListHandler

java.sql.ResultSet の全行を処理したいときは、 ListHandler を使用します。 ListHandlerは jp.sourceforge.sxdbutils.SxRowProcessor インターフェースを引数として受け取り、次のように動作します。

  • 結果が返ってきた時にSxRowProcessor のinitメソッドを呼び出す。
  • ResultSet全行に対してSxRowProcessor のprocessメソッドを呼び出し、その結果をListに入れてListHandler の戻り値として返す。
  • 結果が1件も返ってこなければ、init/processのどちらも呼ばず、空のListを返す。
  • 例:
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. SxRowProcessor<String> processor = new SxRowProcessor<String>() {
    4. public void init(ResultSetMetaData rsmd) throws SQLException {
    5. System.out.println("結果が帰ってきました。");
    6. }
    7. public String process(ResultSet rs) throws SQLException {
    8. return String.format(
    9. "name=%s,
    10. value=%s",
    11. rs.getString("name"),
    12. rs.getString("value"));
    13. }
    14. };
    15. ResultSetHandler<List<String>> rsh
    16. = new ListHandler<String>(processor);
    17. List<String> list
    18. = qr.query(conn, "select * from TABLE01", rsh);
    19. for (String string : list) {
    20. System.out.println(string);
    21. }
    22. }
  • 結果
    結果が帰ってきました。
    name=なまえ1, value=値1
    name=なまえ2, value=値2
    name=なまえ3, value=値3
    name=なまえ4, value=値4
    name=なまえ5, value=値5
    

1行を受け取る場合もListを受け取る場合も、SxRowProcessor は変わりません。 commons-dbutilsでは、このあたりの切り分けがうまくいっておらず、結局拡張性に乏しい実装となっています。


結果をテーブル形式で受け取る

jp.sourceforge.sxdbutils.handlers.ResultTableHandler

SELECT結果全体を、.NETのDataTableのように 2次元の表オブジェクト ResultTable として受け取ることができます。

  1. static void execute(Connection conn) throws Exception {
  2. ResultSetHandler<ResultTable> rsh = new ResultTableHandler();
  3. SxQueryRunner runner = new SxQueryRunner();
  4. ResultTable table
  5. = runner.query(conn, "select * from ALL_TYPES", rsh);
  6. ResultColumnMetaData[] metas = table.getColumnMetaDatas();
  7. for (ResultColumnMetaData meta : metas) {
  8. System.out.println(meta.getColumnName());
  9. }
  10. System.out.println("-------カラム名でデータを取得する--------");
  11. for (ResultRow resultRow : table) {
  12. for (ResultColumnMetaData meta : metas) {
  13. System.out.println(
  14. meta.getColumnName()
  15. + " = "
  16. + resultRow.getString(meta.getColumnName()));
  17. }
  18. }
  19. System.out.println("-------カラム番号でデータを取得する--------");
  20. for (ResultRow resultRow : table) {
  21. for (int i = 0; i < metas.length; i++) {
  22. ResultColumnMetaData meta = metas[i];
  23. System.out.println(
  24. meta.getColumnName()
  25. + " = "
  26. + resultRow.getString(i));
  27. }
  28. }
  29. }

ResultTable

jp.sourceforge.sxdbutils.rstable.ResultTable

ResultTableは2次元の表形式データオブジェクトで、次のような特徴を持っています。

  • jp.sourceforge.sxdbutils.rstable.ResultRow のListとして実装している。(List<ResultRow>)
  • 行番号、カラム番号、カラム名からデータを取得できる。(先頭のインデックスは0となる)
  • getIntやgetBigDecimal、getStringといった、任意のデータ型で取得できるメソッドを用意している。
  • java.sql.ResultSet のように close する必要がない。
  • カラムのメタ情報を取得できる。

ResultTable は、行インデックスやカラムインデックスなどから直接アクセスすることもできますが、java.sql.ResultSetと異なり 0から開始している点に注意してください。
ResultTable によって、commons-dbutilsで一般的に使われている MapHandler / MapListHandler、またSX-DbUtilsのMapRowProcessor など使用するよりも、 高機能で安全性の高いプログラミングが可能となります。

DateTime形式を扱う場合

OracleのDATE型項目を取得した場合、時間部分が消えてしまいます。 これを防ぐにはDate型を取り扱う部分をTimestamp型で扱うように変更します。

ResultTableHandler をnewする際に、次のようにcreateDateTypeColumnメソッドを書き換えます。

  1. ResultSetHandler<ResultTable> rsh = new ResultTableHandler(){
  2. @Override
  3. protected ResultColumn createDateTypeColumn(
  4. ResultSetMetaData meta, int columnIndex)
  5. throws SQLException {
  6. return createTimestampTypeColumn(meta, columnIndex);
  7. }
  8. };


結果をMap形式で受け取る

jp.sourceforge.sxdbutils.handlers.KeyedHandler

ResultSet の1項目がその行を特定するIDとして使用出来る場合、Map形式で受け取ると便利な時があります。 このような場合には、 KeyedHandler を使用できます。

  1. static void execute(Connection conn) throws Exception {
  2. QueryRunner qr = new QueryRunner();
  3. SxRowProcessor<String> processor = new SxRowProcessor<String>() {
  4. public void init(ResultSetMetaData rsmd) throws SQLException {
  5. System.out.println("結果が帰ってきました。");
  6. }
  7. public String process(ResultSet rs) throws SQLException {
  8. return String.format(
  9. "name=%s, value=%s",
  10. rs.getString("name"),
  11. rs.getString("value"));
  12. }
  13. };
  14. ResultSetHandler<Map<String, String>> rsh
  15. = new KeyedHandler<String, String>(
  16. "ID",
  17. String.class,
  18. processor);
  19. Map<String, String> map
  20. = qr.query(conn, "select * from TABLE01", rsh);
  21. for (Map.Entry<String, String> entry : map.entrySet()) {
  22. System.out.printf(
  23. "KEY:%s; VALUE:%s\n",
  24. entry.getKey(),
  25. entry.getValue());
  26. }
  27. }

  • 結果
    結果が帰ってきました。
    KEY:1; VALUE:name=なまえ1, value=値1
    KEY:2; VALUE:name=なまえ2, value=値2
    KEY:3; VALUE:name=なまえ3, value=値3
    KEY:4; VALUE:name=なまえ4, value=値4
    KEY:5; VALUE:name=なまえ5, value=値5
    

commons-dbutilsにも同名のHandlerが存在しますが、取得できる結果がMap<String,Map<String,Object>>で固定されてしまいます。 SxDbUtilsではこのような制限はなく、SxRowProcessor 次第でどのような形式でも扱うことができます。



ResultSetを行単位で変換する

jp.sourceforge.sxdbutils.SxRowProcessor

SX-DbUtilsが提供するResultSetHandler の実装は、コンストラクタの引数として、インターフェース SxRowProcessor を受け取ることが出来ます。 標準で次のような実装を提供しています。

  • ResultSet の1行をBeanに変換する。
  • ResultSet の1行をpublicフィールドを持ったオブジェクトに変換する。
  • ResultSet の1行をMapに変換する。
  • ResultSet の1行を配列に変換する。
  • ResultSet の1行の特定の項目だけ取り出す。


Beanに変換する(1)

jp.sourceforge.sxdbutils.processors.BeanRowProcessor

ResultSet をBeanに変換するには、 BeanRowProcessor を使います。 commons-dbutilsではBeanHandler/BeanListHandlerを使用していましたが、 sx-dbutilsでは BeanRowProcessor とSingleHandler / ListHandlerを組み合わせて使用します。

  • TABLE01をマッピングするBean作成
    1. public class Table01 {
    2. private long id;
    3. private String name;
    4. private String value;
    5. //setter and getter 省略
    6. }
  • TABLE01をTable01クラスにマッピング
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. BeanRowProcessor<Table01> processor
    4. = new BeanRowProcessor<Table01>(Table01.class);
    5. ResultSetHandler<List<Table01>> rsh
    6. = new ListHandler<Table01>(processor);
    7. List<Table01> list
    8. = qr.query(
    9. conn,
    10. "select * from TABLE01",
    11. rsh);
    12. for (Table01 table01 : list) {
    13. System.out.println(
    14. ToStringBuilder.reflectionToString(
    15. table01,
    16. ToStringStyle.SHORT_PREFIX_STYLE));
    17. }
    18. }
  • 結果
    Table01[id=1,name=なまえ1,value=値1]
    Table01[id=2,name=なまえ2,value=値2]
    Table01[id=3,name=なまえ3,value=値3]
    Table01[id=4,name=なまえ4,value=値4]
    Table01[id=5,name=なまえ5,value=値5]
    

このあたりは通常のcommons-dbutilsの通常の使用方法と比べて大差ありません。 ただしcommons-dbutilsは内部で非効率敵な処理が多すぎるため、SX-DbUtilsのほうがパフォーマンス面で優れています。 特にcommons-dbutils-1.1の BeanListHandler の実装はcommons-dbutils-1.0の時よりも処理効率が悪くなっています。 この問題はcommons-dbutils-1.2で修正されましたが、今後も使用するには十分注意した方がいいでしょう。

また、SX-DbUtilsのBeanRowProcessorは、CLOBをStringのプロパティへ、BLOBをbyte[]のプロパティへ、自動的に変換してセットします。


Beanに変換する(2)

jp.sourceforge.sxdbutils.mapping.NameMapping

BeanRowProcessor は、デフォルトで カラム名とBeanのプロパティ名が同じであればマッピングする というルールです。 これはcommons-dbutilsのBeanHandler/BeanListHandler に合わせています。 このルールを変更するには NameMapping を使用します。これはBeanRowProcessor のコンストラクタで指定が可能です。

sx-dbutilsが提供する、NameMappingの標準実装は次のようなものです。

クラス名説明
ColumnNameMapping デフォルトで使用されるNameMappingです。カラム名とプロパティ名が一致した場合にマッピングします。
CamelNameMapping プロパティ名を変換してアンダースコア形式にした名前と、カラム名が一致した場合にマッピングします。
RemoveUnderScoreNameMapping カラム名からアンダースコアを除去した名前と、プロパティ名が一致した場合にマッピングします。
OverwriteNameMapping カラム名とプロパティ名のマッピングをMapで渡してマッピングします。
ConstantAnnoNameMapping 定数アノテーション形式でカラム名を指定してマッピングします。

いずれの場合も大文字小文字の違いは意識しません。 何も指定しなかった場合、ColumnNameMapping が適用されます。


準備

次のようなテーブルがあったとします。

  1. create table TABLE02(
  2. id IDENTITY PRIMARY KEY ,
  3. decimal_value decimal(18),
  4. text_value_1 varchar(255),
  5. text_value_2 varchar(255),
  6. text_value3 varchar(255)
  7. );
  8. insert into TABLE02 (decimal_value,text_value_1,text_value_2,text_value3)
  9. values(10,'テキスト11','テキスト12','テキスト12');
  10. insert into TABLE02 (decimal_value,text_value_1,text_value_2,text_value3)
  11. values(20,'テキスト21','テキスト22','テキスト22');
  12. insert into TABLE02 (decimal_value,text_value_1,text_value_2,text_value3)
  13. values(30,'テキスト31','テキスト32','テキスト32');
  14. insert into TABLE02 (decimal_value,text_value_1,text_value_2,text_value3)
  15. values(40,'テキスト41','テキスト42','テキスト42');
  16. insert into TABLE02 (decimal_value,text_value_1,text_value_2,text_value3)
  17. values(50,'テキスト51','テキスト52','テキスト52');
IDDECIMAL_VALUETEXT_VALUE_1TEXT_VALUE_2TEXT_VALUE3
110テキスト11テキスト12テキスト12
220テキスト21テキスト22テキスト22
330テキスト31テキスト32テキスト32
440テキスト41テキスト42テキスト42
550テキスト51テキスト52テキスト52

このテーブルを次のようなBeanにマッピングしたいとします。

  1. public class Table02 {
  2. private long id;
  3. private BigDecimal decimalValue;
  4. private String textValue1;
  5. private String textValue2;
  6. private String textValue3;
  7. //setter/getter省略
  8. }


アンダースコアを除去した形式でマッピングする。

jp.sourceforge.sxdbutils.mapping.CamelNameMapping

jp.sourceforge.sxdbutils.mapping.RemoveUnderScoreNameMapping

アンダースコアを除去した形式でマッピングするにはCamelNameMapping、もしくはRemoveUnderScoreNameMappingを使用します。 この二つの違いは、次のように数値の前にアンダースコアが入っている場合に現れます。

カラム名プロパティ名Camel...RemoveUnder...
TEXT_VALUE_1 textValue1
TEXT_VALUE3 textValue3 ×

これは、RemoveUnderScoreNameMappingがカラム名からプロパティ名を探すのに対して、 CamelNameMappingはプロパティ名からカラム名を探すことが原因です。

RemoveUnderScoreNameMappingは"TEXT_VALUE3"というカラム名を変換し、"textvalue3"というプロパティ名を探します。 大文字小文字は無視されるため、textValue3プロパティとマッピングされます。 CamelNameMappingは"textValue3"というプロパティ名を変換し、"TEXT_VALUE_3"というカラム名を探します。 このようなカラム名が存在しないため、マッピング出来ません。

CamelNameMappingは一見不便に見えますが、プロパティ名からカラム名が確定するという特徴から、 後述するINSERT/UPDATE文を作成するときに使用することができます。

    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. BeanRowProcessor<Table02> camelProcessor
    4. = new BeanRowProcessor<Table02>(
    5. Table02.class, new CamelNameMapping());
    6. BeanRowProcessor<Table02> ruscoreProcessor
    7. = new BeanRowProcessor<Table02>(
    8. Table02.class,
    9. new RemoveUnderScoreNameMapping());
    10. System.out.println("[CamelNameMapping]--------------------");
    11. List<Table02> camelResult
    12. = qr.query(
    13. conn,
    14. "select * from TABLE02",
    15. new ListHandler<Table02>(camelProcessor));
    16. for (Table02 table02 : camelResult) {
    17. System.out.println(
    18. ToStringBuilder.reflectionToString(
    19. table02,
    20. ToStringStyle.SHORT_PREFIX_STYLE));
    21. }
    22. System.out.println("[RemoveUnderScoreNameMapping]---------");
    23. List<Table02> ruscoreResult
    24. = qr.query(
    25. conn,
    26. "select * from TABLE02",
    27. new ListHandler<Table02>(ruscoreProcessor));
    28. for (Table02 table02 : ruscoreResult) {
    29. System.out.println(
    30. ToStringBuilder.reflectionToString(
    31. table02,
    32. ToStringStyle.SHORT_PREFIX_STYLE));
    33. }
    34. }
  • 結果
    [CamelNameMapping]--------------------
    Table02[id=1,decimalValue=10,textValue1=テキスト11,textValue2=テキスト12,textValue3=<null>]
    Table02[id=2,decimalValue=20,textValue1=テキスト21,textValue2=テキスト22,textValue3=<null>]
    Table02[id=3,decimalValue=30,textValue1=テキスト31,textValue2=テキスト32,textValue3=<null>]
    Table02[id=4,decimalValue=40,textValue1=テキスト41,textValue2=テキスト42,textValue3=<null>]
    Table02[id=5,decimalValue=50,textValue1=テキスト51,textValue2=テキスト52,textValue3=<null>]
    [RemoveUnderScoreNameMapping]---------
    Table02[id=1,decimalValue=10,textValue1=テキスト11,textValue2=テキスト12,textValue3=テキスト12]
    Table02[id=2,decimalValue=20,textValue1=テキスト21,textValue2=テキスト22,textValue3=テキスト22]
    Table02[id=3,decimalValue=30,textValue1=テキスト31,textValue2=テキスト32,textValue3=テキスト32]
    Table02[id=4,decimalValue=40,textValue1=テキスト41,textValue2=テキスト42,textValue3=テキスト42]
    Table02[id=5,decimalValue=50,textValue1=テキスト51,textValue2=テキスト52,textValue3=テキスト52]
    


Mapを使用してカラム名を指定する

jp.sourceforge.sxdbutils.mapping.OverwriteNameMapping

上の アンダースコアを除去した形式でマッピングする。 にて、CamelNameMappingを使用した際、TEXT_VALUE3がマッピング出来ませんでした。 これに次のようなルールが適用したいとします。

  • "textValue3"は"TEXT_VALUE3"とマッピングする。
  • それ以外はCamelNameMappingでマッピングする。

このような「一部だけマッピング出来ない」といった場合、OverwriteNameMapping を使用出来ます。

    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. //マッピングをMapで作る。
    4. Map<String, String> map = new HashMap<String, String>();
    5. map.put("textValue3", "TEXT_VALUE3");
    6. //mapにマッピング情報が無ければCamelNameMappingが適用される。
    7. NameMapping nameMapping = new OverwriteNameMapping(
    8. map,
    9. new CamelNameMapping());
    10. BeanRowProcessor camelProcessor = new BeanRowProcessor(
    11. Table02.class,
    12. nameMapping);
    13. List<Table02> camelResult = (List<Table02>) qr.query(
    14. conn,
    15. "select * from TABLE02",
    16. new ListHandler(camelProcessor));
    17. for (Table02 table02 : camelResult) {
    18. System.out.println(
    19. ToStringBuilder.reflectionToString(
    20. table02,
    21. ToStringStyle.SHORT_PREFIX_STYLE));
    22. }
    23. }
  • 結果
    Table02[id=1,decimalValue=10,textValue1=テキスト11,textValue2=テキスト12,textValue3=テキスト12]
    Table02[id=2,decimalValue=20,textValue1=テキスト21,textValue2=テキスト22,textValue3=テキスト22]
    Table02[id=3,decimalValue=30,textValue1=テキスト31,textValue2=テキスト32,textValue3=テキスト32]
    Table02[id=4,decimalValue=40,textValue1=テキスト41,textValue2=テキスト42,textValue3=テキスト42]
    Table02[id=5,decimalValue=50,textValue1=テキスト51,textValue2=テキスト52,textValue3=テキスト52]
    


定数アノテーションでカラム名を指定する

jp.sourceforge.sxdbutils.mapping.ConstantAnnoNameMapping

OverwriteNameMapping で使用したルールは以下の通りでした。

  • "textValue3"は"TEXT_VALUE3"とマッピングする。
  • それ以外はCamelNameMappingでマッピングする。

OverwriteNameMapping では、Mapを使ってこのルールを定義しました。 ConstantAnnoNameMapping を使えば、定数フィールドを使って定義できます。

  • 先ほど登場したTABLE02ですが、次のように、フィールド名+"_COLUMN"という定数を持たせます。
    1. public class Table02 {
    2. private long id;
    3. private BigDecimal decimalValue;
    4. private String textValue1;
    5. private String textValue2;
    6. public static final String textValue3_COLUMN = "TEXT_VALUE3";
    7. private String textValue3;
    8. //setter/getter省略
    9. }
  • これをConstantAnnoNameMappingを使用してマッピングします。
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. // 定数アノテーションが無ければCamelNameMappingが適用される。
    4. NameMapping nameMapping
    5. = new ConstantAnnoNameMapping<Table02>(
    6. Table02.class,
    7. new CamelNameMapping());
    8. BeanRowProcessor<Table02> camelProcessor
    9. = new BeanRowProcessor<Table02>(
    10. Table02.class,
    11. nameMapping);
    12. List<Table02> camelResult
    13. = qr.query(
    14. conn,
    15. "select * from TABLE02",
    16. new ListHandler<Table02>(camelProcessor));
    17. for (Table02 table02 : camelResult) {
    18. System.out.println(
    19. ToStringBuilder.reflectionToString(
    20. table02,
    21. ToStringStyle.SHORT_PREFIX_STYLE));
    22. }
    23. }
  • 結果
    Table02[id=1,decimalValue=10,textValue1=テキスト11,textValue2=テキスト12,textValue3=テキスト12]
    Table02[id=2,decimalValue=20,textValue1=テキスト21,textValue2=テキスト22,textValue3=テキスト22]
    Table02[id=3,decimalValue=30,textValue1=テキスト31,textValue2=テキスト32,textValue3=テキスト32]
    Table02[id=4,decimalValue=40,textValue1=テキスト41,textValue2=テキスト42,textValue3=テキスト42]
    Table02[id=5,decimalValue=50,textValue1=テキスト51,textValue2=テキスト52,textValue3=テキスト52]
    


エンティティの継承

SX-DbUtilsでは、エンティティの継承をサポートしています。

  • 社員テーブル
    IDEMP_NAMEDEPT_ID
    1社員11
    2社員21
    3社員32
    4社員43
    5社員53
    6社員63
  • 部門テーブル
    IDDEPT_NAME
    1部門1
    2部門2
    3部門3
  • SQL
    1. create table EMP(
    2. id IDENTITY PRIMARY KEY ,
    3. emp_name varchar(255),
    4. dept_id BIGINT
    5. );
    6. insert into EMP(emp_name,dept_id) values('社員1',1);
    7. insert into EMP(emp_name,dept_id) values('社員2',1);
    8. insert into EMP(emp_name,dept_id) values('社員3',2);
    9. insert into EMP(emp_name,dept_id) values('社員4',3);
    10. insert into EMP(emp_name,dept_id) values('社員5',3);
    11. insert into EMP(emp_name,dept_id) values('社員6',3);
    12. create table DEPT(
    13. id IDENTITY PRIMARY KEY ,
    14. dept_name varchar(255)
    15. );
    16. insert into DEPT (dept_name) values('部門1');
    17. insert into DEPT (dept_name) values('部門2');
    18. insert into DEPT (dept_name) values('部門3');
  • 次のようなSQLで社員一覧に部門名も出したい。
    1. select
    2. EMP.* ,DEPT.dept_name
    3. from EMP
    4. inner join DEPT on EMP.dept_id = DEPT.id
  • 結果
    IDEMP_NAMEDEPT_IDDEPT_NAME
    1社員11部門1
    2社員21部門1
    3社員32部門2
    4社員43部門3
    5社員53部門3
    6社員63部門3
  • Java
    1. public class Emp {
    2. private long id;
    3. private String emp_name;
    4. private long dept_id;
    5. //setter/getter
    6. }
    7. public class Dept {
    8. private long id;
    9. private String dept_name;
    10. //setter/getter
    11. }
  • Empを拡張したDeptNameEmp
    1. public class DeptNameEmp extends Emp {
    2. private String dept_name;
    3. //setter/getter
    4. }
  • 継承したテーブルを通常通り使う。
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. BeanRowProcessor<DeptNameEmp> processor
    4. = new BeanRowProcessor<DeptNameEmp>(DeptNameEmp.class);
    5. ResultSetHandler<List<DeptNameEmp>> rsh
    6. = new ListHandler<DeptNameEmp>(processor);
    7. List<DeptNameEmp> list = qr.query(conn,
    8. "select EMP.* ,DEPT.dept_name from EMP inner join DEPT on EMP.dept_id = DEPT.id ",
    9. rsh);
    10. for (DeptNameEmp deptNameEmp : list) {
    11. System.out.println(
    12. ToStringBuilder.reflectionToString(
    13. deptNameEmp,
    14. ToStringStyle.SHORT_PREFIX_STYLE));
    15. }
    16. }
  • 結果
    DeptNameEmp[dept_name=部門1,id=1,emp_name=社員1,dept_id=1]
    DeptNameEmp[dept_name=部門1,id=2,emp_name=社員2,dept_id=1]
    DeptNameEmp[dept_name=部門2,id=3,emp_name=社員3,dept_id=2]
    DeptNameEmp[dept_name=部門3,id=4,emp_name=社員4,dept_id=3]
    DeptNameEmp[dept_name=部門3,id=5,emp_name=社員5,dept_id=3]
    DeptNameEmp[dept_name=部門3,id=6,emp_name=社員6,dept_id=3]
    



publicフィールドを持ったオブジェクトに変換する

jp.sourceforge.sxdbutils.processors.FieldRowProcessor

FieldRowProcessorを使用すれば、publicなフィールドにResultSetをマッピングさせることが出来ます。

FieldRowProcessorは、CLOBをStringのフィールドへ、BLOBをbyte[]のフィールドへ、自動的に変換してセットします。



Mapに変換する

jp.sourceforge.sxdbutils.processors.MapRowProcessor

commons-dbutilsの MapHandler/MapListHandler は、MapRowProcessor を使用することで同等の機能を実現できます。

  • MapRowProcessorを使う
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. MapRowProcessor processor = new MapRowProcessor();
    4. ResultSetHandler<List<Map<String, Object>>> rsh
    5. = new ListHandler<Map<String, Object>>(processor);
    6. List<Map<String, Object>> list
    7. = qr.query(conn, "select * from TABLE01", rsh);
    8. for (Map<String, Object> map : list) {
    9. System.out.println(Util.toString(map));
    10. }
    11. }
  • 結果
    {id:1,name:なまえ1,value:値1}
    {id:2,name:なまえ2,value:値2}
    {id:3,name:なまえ3,value:値3}
    {id:4,name:なまえ4,value:値4}
    {id:5,name:なまえ5,value:値5}
    

SX-DbUtilsのMapRowProcessorは、自動的にCLOBをStringへ、BLOBをbyte[]へ変換します。 SX-DbUtils 1.0.8/1.2.5 以降からは ResultTable が使用できるため、MapRowProcessor を必要とするケースはほとんどありません。



特定の項目の値を取り出す

jp.sourceforge.sxdbutils.processors.ValueRowProcessor

commons-dbutils の ScalarHandler/ColumnListHandler は、ValueRowProcessor を使用することで同等の機能を実現できます。

  • ValueRowProcessorをを使う
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. SxRowProcessor<String> processor = new ValueRowProcessor<String>("NAME");
    4. ResultSetHandler<List<String>> rsh = new ListHandler<String>(processor);
    5. List<String> list = qr.query(conn, "select * from TABLE01", rsh);
    6. for (String name : list) {
    7. System.out.println(name);
    8. }
    9. }
  • 結果
    なまえ1
    なまえ2
    なまえ3
    なまえ4
    なまえ5
    

SX-DbUtilsの ValueRowProcessor は、自動的にCLOBをStringへ、BLOBをbyte[]へ変換します。



SQLを簡単に扱う。


Queryオブジェクト

jp.sourceforge.sxdbutils.query.Query

SX-DbUtilsでは、SQLとバインド変数を一緒に扱う Query オブジェクトを用意しています。

Queryオブジェクトは、オブジェクト配列とStringから構成される普遍クラスです。 joinメソッドで、Queryオブジェクト同士を結合出来ますが、新しいQueryオブジェクトが生成されます。

Queryオブジェクトは文字列化の際、わかりやすい情報として表示させるだけでなく、リテラル表記のSQLを出力できることで、デバッグ効率を上げています。

  • 簡単なQueryオブジェクトの生成
    1. public static void main(String[] args) {
    2. List<Object> parameters=new ArrayList<Object>();
    3. parameters.add("SAMPLE");
    4. Query query = new Query("select * from HOGE WHERE ID=?",parameters);
    5. System.out.println("そのまま表示");
    6. System.out.println(query);
    7. System.out.println("リテラルで表示");
    8. System.out.println(query.literal());
    9. }


SimpleQueryBuilder

jp.sourceforge.sxdbutils.query.SimpleQueryBuilder

SimpleQueryBuilderを使用すると、以下のように簡単にQueryを生成できます。

    1. public static void main(String[] args) {
    2. SimpleQueryBuilder builder = new SimpleQueryBuilder();
    3. builder.append("select * from HOGE")
    4. .append(" WHERE ID=?")
    5. .bind("SAMPLE");
    6. Query query = builder.toQuery();
    7. System.out.println("そのまま表示");
    8. System.out.println(query);
    9. System.out.println("リテラルで表示");
    10. System.out.println(query.literal());
    11. }
  • 実際にSQLを発行する例
    1. static void execute(Connection conn) throws Exception {
    2. QueryRunner qr = new QueryRunner();
    3. BeanRowProcessor<Table01> processor
    4. = new BeanRowProcessor<Table01>(Table01.class);
    5. ResultSetHandler<List<Table01>> rsh
    6. = new ListHandler<Table01>(processor);
    7. SimpleQueryBuilder builder = new SimpleQueryBuilder();
    8. builder.append("select * from TABLE01 where ID=?").bind(1);
    9. Query query = builder.toQuery();
    10. List<Table01> list
    11. = qr.query(
    12. conn,
    13. query.getSql(),
    14. rsh,
    15. query.getParameters());
    16. for (Table01 table01 : list) {
    17. System.out.println(
    18. ToStringBuilder.reflectionToString(
    19. table01,
    20. ToStringStyle.SHORT_PREFIX_STYLE));
    21. }
    22. }

SimpleQueryBuilder でQueryを作成し、QueryからSQLとパラメータを取り出し、QueryRunner で実行するのが、通常の使い方です。 また、SxQueryRunner を使用すると、直接Queryを渡すことができます。


SimpleInsertBuilder / SimpleUpdateBuilder

jp.sourceforge.sxdbutils.query.SimpleInsertBuilder

jp.sourceforge.sxdbutils.query.SimpleUpdateBuilder

SimpleInsertBuilder および SimpleUpdateBuilder は、SQL文字列を手動で組み立てる際に便利なクラスです。

  • SimpleInsertBuilder
    1. static void execute(Connection conn) throws Exception {
    2. SimpleInsertBuilder builder = new SimpleInsertBuilder("TABLE01");
    3. Query query =
    4. builder.add("NAME", "追加する名前")
    5. .add("VALUE", "追加する値")
    6. .toQuery();
    7. System.out.println(query);
    8. }
  • 結果
    insert into TABLE01 (NAME,VALUE )  values( ?,?)
    ==parameter====================
    000    java.lang.String    #追加する名前
    001    java.lang.String    #追加する値
    ====================parameter==
    
  • SimpleUpdateBuilder
    1. static void execute(Connection conn) throws Exception {
    2. SimpleUpdateBuilder builder = new SimpleUpdateBuilder("TABLE01");
    3. Query query = builder
    4. .addUpdateColumn("NAME", "更新する名前")
    5. .addUpdateColumn("VALUE", "更新する値")
    6. .addWhereColumn("ID", 6)
    7. .toQuery();
    8. System.out.println(query);
    9. }
  • 結果
    update TABLE01 set NAME=?,VALUE=? where ID=?
    ==parameter====================
    000    java.lang.String    #更新する名前
    001    java.lang.String    #更新する値
    002    java.lang.Integer    #6
    ====================parameter==
    



QueryFactory

QueryFactoryはインスタンス作成時にSQLを保持しておき、Queryオブジェクトを高速に生成することができます。 QueryFactoryはQueryFactoryBuuilderを使用して生成し、staticなフィールドなどに保持しておくことを推奨します。

  • BeanからINSERT文を発行する例
    1. static QueryFactory insertFactory =
    2. new BeanQueryFactoryBuilder(Table01.class)
    3. .excludeColumn("ID")
    4. .buildInsert();
    5. public static void main(String[] args) {
    6. Table01 table01 = new Table01();
    7. table01.setName("なまえ");
    8. table01.setValue("あたい");
    9. Query query=insertFactory.toQuery(table01);
    10. System.out.println(query);
    11. }
  • 結果
    insert into Table01(name,value )  values ( ?,? ) 
    ==parameter====================
    000    java.lang.String    #なまえ
    001    java.lang.String    #あたい
    ====================parameter==
    
  • MapからINSERT文を発行する例
    1. static QueryFactory insertFactory =
    2. new MapQueryFactoryBuilder("TABLE02")
    3. .addColumn("decimal_value")
    4. .addColumn("text_value_1")
    5. .addColumn("text_value_2")
    6. .addColumn("text_value3")
    7. .buildInsert();
    8. public static void main(String[] args) {
    9. Map<String, Object> map = new HashMap<String, Object>();
    10. map.put("decimal_value", "なまえ");
    11. map.put("text_value_1", "あたい1");
    12. map.put("text_value_2", "あたい2");
    13. map.put("text_value3","あたい3");
    14. Query query = insertFactory.toQuery(map);
    15. System.out.println(query);
    16. }
  • 結果
    insert into TABLE02(decimal_value,text_value_1,text_value_2,text_value3 )  values ( ?,?,?,? ) 
    ==parameter====================
    000    java.lang.String    #なまえ
    001    java.lang.String    #あたい1
    002    java.lang.String    #あたい2
    003    java.lang.String    #あたい3
    ====================parameter==
    



その他


マッピングについて


数値型カラムのマッピング

Types/class int long byte short float double char ※1 BigDecimal String ※1 boolean ※1
BIGINT
DECIMAL
DOUBLE
FLOAT
INTEGER
NUMERIC
REAL
SMALLINT
TINYINT

○正しくマッピングできる。 △マッピングできるが、精度が落ちる可能性がある。 ●特定のルールに従い、マッピングされる。 ※1 Bean → DBでは、SQL型を指定する必要があります。

プリミティブのラッパークラスにも対応しています。プリミティブはnullのときにデフォルトがセットされますが、ラッパークラスはnullをがセットされます。


文字列型カラムのマッピング

charString
VARCHAR
CHAR
CLOB×


バイナリカラムのマッピング

バイナリの項目(BLOBなど)は、byte[]へマッピングされます。



Java5対応について

commons-dbutils-1.2まではJava1.4ベースでした。 そのため sx-dbutils.jar の他に、sx-dbutils-tiger.jar が必要となります。

  • jp.sourceforge.sxdbutils.tiger

このパッケージ内に用意されたクラスを使用することでジェネリクスを利用したコーディングが可能となります。

commons-dbutils-1.3 からはJava5に依存しています。 Sx-DbUtilsも1.3からはJava5対応をしていますので、sx-dbutils-tiger.jar は不要になります。



template

Template機能は今後、機能を見直す予定です。
自由度を求めた結果、使用方法が複雑になりすぎてしまいました。

template 機能を使うことで、より簡単にsx-dbutilsを使用することができます。 template 機能は、sx-dbutilsを使用したDaoパターンの標準構成です。

  • jp.sourceforge.sxdbutils.tiger.template


サンプル


基本構成

  • テーブル USERS
    1. create table USERS(
    2. id identity primary key ,
    3. user_cd varchar(10) not null,
    4. name varchar(255),
    5. dept_id bigint default 0 not null,
    6. age integer default 0 not null,
    7. description clob,
    8. add_user_id bigint default 0 not null,
    9. add_timestamp datetime,
    10. update_user_id bigint default 0 not null,
    11. update_timestamp datetime,
    12. version_no integer default 0 not null
    13. );
  • クラス User
    1. import java.util.Date;
    2. public class Users {
    3. private long id;
    4. private String userCd;
    5. private String name;
    6. private long deptId;
    7. private int age;
    8. private String description;
    9. private long addUserId;
    10. private Date addTimestamp;
    11. private long updateUserId;
    12. private Date updateTimestamp;
    13. private int versionNo;
    14. ・・・・
    15. //setter.getter


準備

プロジェクトごとに多少要件が異なるため、次のようなDaoの基底クラスを準備します。 ここではSpringフレームワークを使ってDataSourceをDIしています。

  • クラス ProjectBaseTemplate
    1. import java.sql.Connection;
    2. import java.sql.SQLException;
    3. import javax.annotation.Resource;
    4. import javax.sql.DataSource;
    5. import jp.sourceforge.sxdbutils.query.QueryFactory;
    6. import jp.sourceforge.sxdbutils.tiger.template.AbstractCrudTemplate;
    7. import org.springframework.jdbc.datasource.DataSourceUtils;
    8. /*
    9. * プロジェクトごとにDataSourceやConnectionの取得方法は異なるので、このようなクラスで違いを吸収する。
    10. */
    11. public abstract class ProjectBaseTemplate<E> extends
    12. AbstractCrudTemplate<E> {
    13. public ProjectBaseTemplate(
    14. QueryFactory insertFactory,
    15. QueryFactory updateFactory,
    16. QueryFactory deleteFactory) {
    17. super(insertFactory, updateFactory, deleteFactory);
    18. }
    19. private DataSource dataSource;
    20. @Resource(name = "dataSource")
    21. public void setDataSource(DataSource dataSource) {
    22. this.dataSource = dataSource;
    23. }
    24. @Override
    25. protected Connection getConnection() throws SQLException {
    26. return DataSourceUtils.getConnection(dataSource);
    27. }
    28. }

Daoを実装する

  • インターフェース UserDao
    1. import java.sql.SQLException;
    2. import java.util.List;
    3. import jp.sourceforge.sxdbutils.tiger.template.InsertTemplate;
    4. import jp.sourceforge.sxdbutils.tiger.template.SelectTemplate;
    5. import jp.sourceforge.sxdbutils.tiger.template.UpdateTemplate;
    6. import sample.entity.Users;
    7. /*
    8. * ここではDeleteTemplateを使用しないことで、削除禁止としている。
    9. */
    10. public interface UserDao extends InsertTemplate<Users>, UpdateTemplate<Users>,
    11. SelectTemplate<Users> {
    12. public abstract List<Users> selectAll() throws SQLException;
    13. public abstract Users selectByUserCd(String userCd) throws SQLException;
    14. }
  • Dao実装 UserDaoImpl
    1. import java.sql.SQLException;
    2. import java.util.List;
    3. import jp.sourceforge.sxdbutils.mapping.CamelNameMapping;
    4. import jp.sourceforge.sxdbutils.tiger.processors.BeanRowProcessor;
    5. import jp.sourceforge.sxdbutils.query.BeanQueryFactoryBuilder;
    6. import jp.sourceforge.sxdbutils.query.Query;
    7. import jp.sourceforge.sxdbutils.query.QueryFactory;
    8. import jp.sourceforge.sxdbutils.query.SimpleQueryBuilder;
    9. import jp.sourceforge.sxdbutils.tiger.SxRowProcessor;
    10. import org.springframework.stereotype.Repository;
    11. import sample.ProjectBaseTemplate;
    12. import sample.entity.Users;
    13. @Repository
    14. public class UserDaoImpl extends ProjectBaseTemplate<Users> implements UserDao {
    15. private static final QueryFactory insertFactory =
    16. new BeanQueryFactoryBuilder(
    17. Users.class,
    18. new CamelNameMapping())
    19. .excludeColumn("ID")
    20. .excludeColumn("UPDATE_USER_ID")
    21. .excludeColumn("UPDATE_TIMESTAMP")
    22. .buildInsert();
    23. private static final QueryFactory updateFactory =
    24. new BeanQueryFactoryBuilder(
    25. Users.class,
    26. new CamelNameMapping())
    27. .excludeColumn("ADD_USER_ID")
    28. .excludeColumn("ADD_TIMESTAMP")
    29. .whereKeyColumn("ID")
    30. .versionColumnName("VERSION_NO")
    31. .buildUpdate();
    32. //DeleteTemplateは呼ばれないので、実質このFactoryは無効。
    33. //ここではサンプルとして記述してある。
    34. private static final QueryFactory deleteFactory =
    35. new BeanQueryFactoryBuilder(
    36. Users.class,
    37. new CamelNameMapping())
    38. .whereKeyColumn("ID")
    39. .versionColumnName("VERSION_NO")
    40. .buildDelete();
    41. public UserDaoImpl() {
    42. super(insertFactory, updateFactory, deleteFactory);
    43. }
    44. @Override
    45. protected Class<Users> getEntityClass() {
    46. return Users.class;
    47. }
    48. @Override
    49. protected <X extends Users> SxRowProcessor<X> createRowProcessor(
    50. Class<X> beanClass) {
    51. return new BeanRowProcessor<X>(beanClass, new CamelNameMapping());
    52. }
    53. // -----------------------------------------------------------
    54. /*
    55. * (non-Javadoc)
    56. *
    57. * @see sample.dao.UserDao#selectAll()
    58. */
    59. public List<Users> selectAll() throws SQLException {
    60. SimpleQueryBuilder builder = new SimpleQueryBuilder();
    61. Query query = builder.append("select * from USERS").toQuery();
    62. return executeQueryToEntityList(query);
    63. }
    64. @Override
    65. public Users selectByUserCd(String userCd) throws SQLException {
    66. SimpleQueryBuilder builder = new SimpleQueryBuilder();
    67. Query query = builder.append(
    68. "select * from USERS where USER_CD =?",
    69. userCd).toQuery();
    70. return executeQueryToBean(query);
    71. }
    72. }