2012 02 14

車輪再び

職場の隣の席の人が、インフルエンザで今日から休み。 たぶん今週いっぱいは強制的に休み。 気の毒なような、羨ましいような。 いや、気の毒なのは、休みの間にそいつの分をフォローしなきゃいけない俺の方か。 まあ、フォローって言う程やることがある訳でもないのだが。

フォローとは関係無いが、仕事柄、いろいろと古いプログラムを調べることが多い。 で、その度に、割と高い確率でうんざりする。 変更内容を残すためのコメントが多過ぎてソースコードが読み難いとか、よく見たらコメントが嘘とか。 ほんの一部が違うだけのほぼ同じ処理が散在してるとか、対象は違ってもやってることは同じなのがしつこく繰り返されてるとか。

コメントは無視すれば良い。 あまりに読み難い時は、調査用にコピーしてコメントを全削除すればいいし。 でも、コードが駄目なのは厳しい。 無視できない分だけ消耗も激しいのだ。 自分の中の大切な何かが、凄い勢いで擦り切れて行く気がする。

そんなストレス解消のために、気分転換に作ってみたのがこれ。 DBから取得した ResultSet を、指定の DTO に変換する。

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ResultSetConverter<E> { private final Map<String, Method> setters = new HashMap<>(); public ResultSetConverter(final ResultSet rs, final Class<E> dtoClass) throws Exception { for (String column : getColumns(rs)) { try { setters.put(column, dtoClass.getMethod(toSetterName(column), new Class[] { String.class })); } catch (SecurityException | NoSuchMethodException e) { // ignore } } } public E toDTO(final ResultSet rs, final E dto) throws SQLException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Object[] parameter = new Object[1]; for (String column : setters.keySet()) { parameter[0] = rs.getString(column); setters.get(column).invoke(dto, parameter); } return dto; } private static List<String> getColumns(final ResultSet rs) throws SQLException { List<String> list = new ArrayList<>(); ResultSetMetaData rsMetaData = rs.getMetaData(); int columnCount = rsMetaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { list.add(rsMetaData.getColumnName(i)); } return list; } private static String toSetterName(final String columnName) { StringBuilder setterName = new StringBuilder("set"); for (String part : columnName.split("_")) { setterName.append(part.substring(0, 1).toUpperCase()); setterName.append(part.substring(1).toLowerCase()); } return setterName.toString(); } }

commons.BeanUtil 辺りに似たようなものを見た気がするが、細かいことは気にしない。 大切なのは、自分の手を動かすことなのだ。

コンストラクターは、ResultSet と DTO のクラスを受け取って、列とセッターの対応をマップに保存する。 制約は以下の通り。

メソッド toDTO で、1レコードをDTOに変換する。 引数で渡したDTOのインスタンスに値を設定して戻値とするのは、内部で自動生成するよりも外から渡した方が柔軟と思ったからなのだが、なんかちょっと微妙な仕様だな。

で、こんな感じで使う。

public List<User> getUsers(Connection conn) throws Exception { List<User> users = new ArrayList<>(); try (Statement stmt = conn.createdStatement()) { ResultSet rs = stmt.executeQuery("SELECT USER_ID, AUTH_CD, SECT_CD FROM USER_MST"); ResultSetConverter<User> converter = new ResultSetConverter<>(rs, User.class); while (rs.next()) { users.add(converter.toDTO(rs, new User()); } } catch (Exception e) { e.printStackTrace(); throw e; } return Users; }

具体的に何が楽になるかと言うと、上の7行目の1行で済んでいるところが、使わないとこうなる。

while (rs.next()) { User user = new User(); user.setUserId(rs.getString("USER_ID")); user.setAuthCd(rs.getString("AUTH_CD")); user.setSectCd(rs.getString("SECT_CD")); users.add(user); }

これだと列が3つなので大した効果もないのだが、今見ているコードにはこれが300行ぐらい続くところもあって、改善効果はそれなりに高いのだ。

って、まあ、使わないんだけどね。 使うときっと楽になるんだろうなぁ… と思いながら眺めるだけなんだけど。