Java: Re: ダイナミックプロキシ

誰か書くかと思っていたのだけれど、私の巡回範囲で誰もやらなかったので自分で書いてみた。
ダイナミックプロキシを使って、簡易なO/R Mapperを作る。
interfaceを使って、インターフェース名がテーブル名、メソッド名がカラム名というルールにして、動的にSQLを作って実行して、ダイナミックプロキシでそのインターフェースを実装したオブジェクトを作成するというもの。
まずは、前半の実行部分。EMPLOYEEテーブルにNAMEカラムとSALARYカラムがあるという設定。書かなければいけないのは、interface Employeeだけ。

import java.lang.reflect.*;
import java.sql.*;
import java.util.*;

interface Employee {
  String name();
  int salary();
}

public class ORMapping {
  public static void main (Connection conn)
  throws SQLException
  {
    Mapper<Employee> mapper = null;
    try{
      mapper = Mapper.create(conn, Employee.class);
      Employee emp;
      while (null != (emp = mapper.fetch())){
        System.out.println("name=" + emp.name() + ", salary=" + emp.salary());
      }
    }finally{
      if (mapper != null) mapper.close();
    }
  }
}

以下、ダイナミックプロキシを使った実装。ただし、動かして検証していないので、バグっている可能性あります。

class ResultProxy implements InvocationHandler {
  private final HashMap<String, Object> data;
  ResultProxy (HashMap<String, Object> data) { this.data = data;}
  public Object invoke (Object proxy, Method m, Object[] args)
  throws Throwable
  {
    return data.get(m.getName());
  }
}

class Mapper<A> {
  static<A> Mapper<A> create (Connection conn, Class<A> intf)
  throws SQLException
  {
    return new Mapper<A>(conn, intf);
  }
  private Mapper (Connection conn, Class<A> intf)
  throws SQLException
  {
    this.intf = intf;
    tbl = intf.getName();
    Method[] ms = intf.getDeclaredMethods();
    cols = new String[ms.length];
    for (int i=0; i<ms.length; ++i){
      cols[i] = ms[i].getName();
    }
    StringBuffer sql = new StringBuffer("SELECT ").append(cols[0]);
    for (int i=1; i<cols.length; ++i){
      sql.append(",").append(cols[i]);
    }
    sql.append(" FROM ").append(tbl);
    ps = conn.prepareStatement(sql.toString());
    rs = ps.executeQuery();
  }
  private Class<A> intf;
  private String tbl;
  private String[] cols;
  private PreparedStatement ps;
  private ResultSet rs;

  @SuppressWarnings("unchecked")
  A fetch ()
  throws SQLException
  {
    if (! rs.next()) return null;
    HashMap<String, Object> data = new HashMap<String, Object>(cols.length);
    for (int i=0; i<cols.length; ++i){
      data.put(cols[i], rs.getObject(i+1));
    }
    return (A) java.lang.reflect.Proxy.newProxyInstance
      (intf.getClassLoader(), new Class[] {intf}, new ResultProxy(data));
  }

  void close ()
  throws SQLException
  {
    if (ps != null){
      ps.close();
      ps = null;
    }
  }
}