※内容を改変しました。(2018/7/30)
Java6で、ResultSetをbeanにマッピングする処理を自作します。
Frameworkを使えないので、自作してみることにしました。
レスポンスも気になるので、beanにマッピングする場合と、Stringの配列にマッピングする場合との
2つのクラスを作成して、処理速度を計測します。
1.テストするテーブル
30項目くらいの列数をもったテーブルをテストに使います。
sampledatabase.testtable
create table public.testtable (
col1 character(8)
, col2 character varying(8)
, col3 numeric(10, 2)
, col4 numeric(10, 2)
, col5 bit(1)
, col6 integer
, col7 bigint
, col9 date
, col10 timestamp without time zone
, col21 character(8)
, col22 character varying(8)
, col23 numeric(10, 2)
, col24 numeric(10, 2)
, col25 bit(1)
, col26 integer
, col27 bigint
, col29 date
, col30 timestamp without time zone
, col31 character(8)
, col32 character varying(8)
, col33 numeric(10, 2)
, col34 numeric(10, 2)
, col35 bit(1)
, col36 integer
, col37 bigint
, col39 date
, col40 timestamp without time zone
);
2.マッピングする場合
(1)Beanの配列にセットするクラスを作成します
GenericBeanArray.java
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class GenericBeanArray {
/**
* 項目数
*/
private int columnCount=0;
/**
* 行数
*/
private long rowCount=0;
/**
* ResultSetMetaData.getColumnTypeに対応したjavaのクラス名をmapに格納する。
*/
@SuppressWarnings("rawtypes")
private HashMap<Integer, Class> _setterArg=null;
/**
* ResultSetのRowsを配列化
*/
@SuppressWarnings("rawtypes")
private List list = new ArrayList();
@SuppressWarnings({ "rawtypes", "unchecked" })
public GenericBeanArray(ResultSet rs, Class clazz) throws Exception {
/** * columnTypeと型のmap */
_setterArg = new HashMap<Integer, Class>();
_setterArg.put(2, BigDecimal.class);
_setterArg.put(-7, boolean.class);
_setterArg.put(4, int.class);
_setterArg.put(-5, long.class);
_setterArg.put(91, Date.class);
_setterArg.put(93, Timestamp.class);
_setterArg.put(1, String.class);
_setterArg.put(12, String.class);
//Beanのクラス名
String beanClassName=clazz.getName();
//MetaData
ResultSetMetaData rsmd = rs.getMetaData();
this.setColumnCount(rsmd.getColumnCount());
this.rowCount=0;
while (rs.next()) {
//Beanインスタンスを生成
Class c = Class.forName(beanClassName);
Object row = (Object)c.newInstance();
this.rowCount++;
//全ての列情報を取得して、setterメソッドを動的に実行する。
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
//setterメソッド名を取得する。
String methodName = getMethodName(rsmd.getColumnName(i));
//setterメソッドの引数
Class[] classArgs = getMethodArgs(rsmd.getColumnType(i));
//メソッドオブジェクトを生成
Method mthod =
Class.forName(beanClassName).getMethod(methodName, classArgs);
//メソッドを実行する。
mthod.invoke(row, new Object[]{rs.getObject(i)});
}
list.add(row);
}
}
/**
* setterメソッド名のsetXXXの先頭文字列を大文字にします。
* @param columnName 列名
* @return 引数の先頭の文字を大文字に変換した文字列
*/
private String upperFirstChar(String columnName) {
if (columnName==null || columnName.isEmpty()) {
return columnName;
}
return String.format("%S", columnName.substring(0, 1))
+ columnName.substring(1);
}
/**
* (beanの)setterメソッド名取得
* @param columnName 項目名
* @return setterメソッド名
*/
private String getMethodName(String columnName) {
return "set" + upperFirstChar(columnName);
}
/**
* setterメソッドの引数を取得する。
* @param columnType 項目名
* @return setterメソッド引数オブジェクト
*/
@SuppressWarnings("rawtypes")
private Class[] getMethodArgs(int columnType) {
//型に対応する各クラスはMapから取得する。
return new Class[]{_setterArg.get(columnType)};
}
public int getColumnCount() {
return columnCount;
}
public void setColumnCount(int columnCount) {
this.columnCount = columnCount;
}
public long getRowCount() {
return rowCount;
}
public void setRowCount(long rowCount) {
this.rowCount = rowCount;
}
@SuppressWarnings("rawtypes")
public HashMap<Integer, Class> get_setterArg() {
return _setterArg;
}
@SuppressWarnings("rawtypes")
public void set_setterArg(HashMap<Integer, Class> _setterArg) {
this._setterArg = _setterArg;
}
@SuppressWarnings("rawtypes")
public List getList() {
return list;
}
@SuppressWarnings("rawtypes")
public void setList(List list) {
this.list = list;
}
}
(2)Bean
マッピングするbean
TestTable.java
public class TestTable {
private String col1;
private String col2;
private java.math.BigDecimal col3;
private java.math.BigDecimal col4;
private boolean col5;
private int col6;
private long col7;
private java.sql.Date col9;
private java.sql.Timestamp col10;
private String col21;
private String col22;
private java.math.BigDecimal col23;
private java.math.BigDecimal col24;
private boolean col25;
private int col26;
private long col27;
private java.sql.Date col29;
private java.sql.Timestamp col30;
private String col31;
private String col32;
private java.math.BigDecimal col33;
private java.math.BigDecimal col34;
private boolean col35;
private int col36;
private long col37;
private java.sql.Date col39;
private java.sql.Timestamp col40;
@Override
public String toString() {
return
this.getCol1() + "," +
this.getCol2() + "," +
this.getCol3() + "," +
this.getCol4() + "," +
this.isCol5() + "," +
this.getCol6() + "," +
this.getCol7() + "," +
this.getCol9() + "," +
this.getCol10() + "," +
this.getCol21() + "," +
this.getCol22() + "," +
this.getCol23() + "," +
this.getCol24() + "," +
this.isCol25() + "," +
this.getCol26() + "," +
this.getCol27() + "," +
this.getCol29() + "," +
this.getCol30() + "," +
this.getCol31() + "," +
this.getCol32() + "," +
this.getCol33() + "," +
this.getCol34() + "," +
this.isCol35() + "," +
this.getCol36() + "," +
this.getCol37() + "," +
this.getCol39();
}
//setter / getterは省略
}
3.Stringの配列にマッピングする場合
(1)Stringの配列にマッピングするクラス
QuerySet.java
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* データを管理するクラス
* @author ycnp4
*
*/
public class QuerySet {
/**
* 項目数
*/
private int columnCount=0;
/**
* 行数
*/
private long rowCount=0;
/*** ResultSetMetaDataの項目名を管理する。
* key:列番号
* val:getColumnTypeName
* */
private HashMap<Integer, String> columnNameMap
=new HashMap<Integer, String>();
/*** ResultSetMetaDataの各項目の型名を管理する。
* key:列番号
* val:getColumnTypeName
* */
private HashMap<Integer, String> columnTypenameMap
=new HashMap<Integer, String>();
/**
* 検索結果のレコードの配列
*/
private List<String[]> rows=new ArrayList<String[]>();
/**
* コンストラクタ
* @param rs ResultSetオブジェクト
* @throws Exception 例外
*/
public QuerySet(ResultSet rs) throws Exception {
//metadataを取得する。
ResultSetMetaData meta = rs.getMetaData();
this.columnCount=meta.getColumnCount();
for (int i = 1; i <= meta.getColumnCount(); i++) {
columnNameMap.put(i, meta.getColumnName(i));
columnTypenameMap.put(i, meta.getColumnTypeName(i));
}
this.rowCount=0;
while (rs.next()) {
this.rowCount++;
List<String> rowdata=new ArrayList<String>();
for (int i = 1; i <= meta.getColumnCount(); i++) {
rowdata.add(rs.getString(i));
}
this.addRow(rowdata.toArray(new String[rowdata.size()]));
}
}
/**
* 行追加
* @param row
*/
public void addRow(String[] row) {
this.rows.add(row);
}
}
//setter / getterは省略
(2)テスト用に、Stringの配列を結合して文字列にするクラス
CommonUtils.java
public class CommonUtils {
public static String join(String delimiter, Object... strarr){ //可変長引数
String rv = "";
for(Object sa: strarr){ //拡張for
rv += sa + delimiter;
}
return rv.substring(0, rv.length() - delimiter.length()); //String切り出し
}
}
4.DB接続クラス
両方のオブジェクトを返すメソッドを実装しています。
DBUtils.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtils {
private Connection _connection = null;
/**
* コンストラクタ(仮)
* @throws SQLException
*/
public DBUtils() throws SQLException {
_connection = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/sampledatabase",
"testuser",
"XXXXXXXXXXXXXXXX");
}
/**
* クエリを実行してをBeanのListに詰めて返します。
* @param query クエリ(SQL)
* @param clazz Beanのクラス
* @return BeanのList
* @throws Exception 例外はすべてスロー
*/
@SuppressWarnings("rawtypes")
public GenericBeanArray query(String query, Class clazz) throws Exception {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
//PreparedStatementを生成
stmt = _connection.prepareStatement(query);
//ResultSetを取得する。
rs = stmt.executeQuery();
return new GenericBeanArray(rs, clazz);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
close(rs);
close(stmt);
}
}
/**
* クエリを実行して、QuerySetオブジェクトを生成します。
* @param query クエリ
* @return QuerySetオブジェクト
* @throws Exception 例外
*/
public QuerySet getQuerySet(String query) throws Exception {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
//PreparedStatementを生成
stmt = _connection.prepareStatement(query);
//ResultSetを取得する。
rs = stmt.executeQuery();
//QuerySetオブジェクトを生成します
return new QuerySet(rs);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
close(rs);
close(stmt);
}
}
/**
* ResultSetクローズ
* @param ps PreparedStatement
*/
private void close(PreparedStatement ps) {
try {
if (ps != null && !ps.isClosed()) {
ps.close();
System.out.println("PreparedStatement closed.");
}
} catch (SQLException e) {
//どうしようもないので何もしなくていい。
e.printStackTrace();
}
}
/**
* ResultSetクローズ
* @param rs ResultSet
*/
private void close(ResultSet rs) {
try {
if (rs != null && !rs.isClosed()) {
rs.close();
System.out.println("ResultSet closed.");
}
} catch (SQLException e) {
//どうしようもないので何もしなくていい。
e.printStackTrace();
}
}
/**
* ファイナライザ
*/
@Override
protected void finalize() throws Throwable {
try {
super.finalize();
} finally {
closeConnection();
}
}
/**
* ファイナライザ
* コネクションをクローズする。
*/
public void closeConnection() {
try {
_connection.close();
System.out.println("connection closed.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5.実行
sampleORMapper.java
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.List;
public class TestOrMapper {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
FileWriter file = null;
PrintWriter pw = null;
FileWriter file2 = null;
PrintWriter pw2 = null;
DBUtils db;
try {
// FileWriterクラスのオブジェクトを生成する
file = new FileWriter("C:\\work\\sampleORMapper\\bean.txt");
// PrintWriterクラスのオブジェクトを生成する
pw = new PrintWriter(new BufferedWriter(file));
// FileWriterクラスのオブジェクトを生成する
file2 = new FileWriter("C:\\work\\sampleORMapper\\queryset.txt");
// PrintWriterクラスのオブジェクトを生成する
pw2 = new PrintWriter(new BufferedWriter(file2));
db = new DBUtils();
long start = System.currentTimeMillis();
GenericBeanArray beanarray =
db.query("select * from testtable", TestTable.class);
int i=0;
long end = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
QuerySet queryset =
db.getQuerySet("select * from testtable");
long end2 = System.currentTimeMillis();
List<TestTable> list = beanarray.getList();
for (TestTable row : list) {
pw.println(row.toString());
}
for (String[] row : queryset.getRows()) {
pw2.println(CommonUtils.join(",", row));
}
System.out.println(
"case bean 列数=" + beanarray.getColumnCount() +"件数=" + beanarray.getRowCount() + " 処理時間=" + (end - start) + "ms");
System.out.println(
"case queryset 列数=" + queryset.getColumnCount() + " 件数=" + queryset.getRowCount() + " 処理時間=" + (end2 - start2) + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
//ファイルを閉じる
pw.close();
pw2.close();
}
}
}
6.結果
case bean 列数=27件数=1500 処理時間=939ms
case queryset 列数=27 件数=1500 処理時間=26ms
beanにmappingすると、String[]に比較して、かなり時間がかかってしまってますね。
beanが必要ない限り、String[]などで行を処理するほうがいいかな・・。