Java6でResultSetとBeanのORMappingを自作する。

※内容を改変しました。(2018/7/30)

Java6で、ResultSetをbeanにマッピングする処理を自作します。
Frameworkを使えないので、自作してみることにしました。

レスポンスも気になるので、beanにマッピングする場合と、Stringの配列にマッピングする場合との
２つのクラスを作成して、処理速度を計測します。

###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[]などで行を処理するほうがいいかな・・。