2008年4月3日木曜日

DbUtils でアンダバー区切りをハンガリアン記法にマッピング

今回は Jakarta Commons の DbUtils について少し書こうと思います。

DbUtils は次のコードサンプルのように SQLクエリの結果を JavaBeans として得ることができるシンプルで便利な機能を持っています。
QueryRunner run = new QueryRunner(dataSource);
ResultSetHandler h = new BeanHandler(Person.class);
Person p = (Person) run.query(
"SELECT * FROM Person WHERE name=?", "John Doe", h);

例えば、Person テーブルの定義が id と name カラムを持つ場合、
create table person (
id integer not null primary key,
name varchar(20) not null
);

Person クラスはこんな感じです。
public class Person implements java.io.Serializable {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

最近 DbUtils を使い始めたのですが、1つ気に入らない問題が出てきました。Java 側の命名規則はハンガリアン記法、データベース側の命名規則はアンダーバー区切りで、次のようなテーブルに対応する JavaBeans は、
create table person (
id integer not null primary key,
first_name varchar(20) not null,
last_name varchar(20) not null
);

first_name カラムは first_name という JavaBeans プロパティにマッピングされるので、専用の setFirst_name などという専用のセッターを用意すればなんとかなりますがあまり美しくありません。
public class Person implements java.io.Serializable {
private int id;
private String firstName;
private String lastName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName= firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName(String lastName) {
this.lastName= lastName;
}
// NOTE: for DbUtils
public void setFirst_name(String firstName) {
setFirstName(firstName);
}
public void setLast_name(String lastName) {
setLastName(lastName);
}
}

上記のサンプルが書かれているサイトの下の方にも記述がありますが、
http://commons.apache.org/dbutils/examples.html
org.apache.commons.dbutils.BeanProcessor の mapColumnsToProperties メソッドをカスタマイズすることで first_name から firstName へのマッピングを行うことができます。
import java.beans.PropertyDescriptor;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import org.apache.commons.dbutils.BeanProcessor;
public class MyBeanProcessor extends BeanProcessor {
@Override
protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
PropertyDescriptor[] props) throws SQLException {
int cols = rsmd.getColumnCount();
int columnToProperty[] = new int[cols + 1];
Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
for (int col = 1; col <= cols; col++) {
String columnName = rsmd.getColumnName(col);
for (int i = 0; i < props.length; i++) {
// NOTE: 元のコードを
// if (columnName.equalsIgnoreCase(props[i].getName())) {
// NOTE: 次のように変更
if (equalsColumnProperty(columnName, props[i].getName())) {
columnToProperty[col] = i;
break;
}
}
}
return columnToProperty;
}
private boolean equalsColumnProperty(String colName, String propName) {
// NOTE: 頻繁に呼び出されるので実際にはキャッシュを使うなどして
// 高速化したほうがよいです。
return colName.replaceAll("_", "").equalsIgnoreCase(propName);
}
}

カスタマイズした MyBeanProcessor の使い方はこんな感じです。実際はRowProcessor のインスタンスはシステムに 1つでいいかと思います。
RowProcessor rp = new BasicRowProcessor(new MyBeanProcessor());
QueryRunner run = new QueryRunner(dataSource);
ResultSetHandler h = new BeanHandler(Person.class, rp);
Person p = (Person) run.query(
"SELECT * FROM Person WHERE first_name=?", "John Doe", h);

0 件のコメント: