【Java】 Apache Commons DbUtils を使いやすくすることを考える

はじめに

http://blogs.yahoo.co.jp/dk521123/36044963.html
http://blogs.yahoo.co.jp/dk521123/34079202.html
http://blogs.yahoo.co.jp/dk521123/36050559.html
http://blogs.yahoo.co.jp/dk521123/36053260.html
などでORMを検討したが、その中で
http://blogs.yahoo.co.jp/dk521123/34079202.html
で紹介したDbUtils をもう少し使いやすい形にしてみる
# ぶっちゃけ上記のコードを整理しただけ。

補足

 * SX DbUtilsはいうのもあるらしいが、メンテされてなさそうなので...
https://osdn.jp/projects/sx-dbutils/

注意

DbUtils.commitAndCloseQuietly(connection)について

 * コミットして Connection オブジェクトを解放するので、
   その後、DB接続すると、例外が発生する
http://java6.blog117.fc2.com/blog-entry-37.html

サンプル

DBデータ(MySQL)

CREATE TABLE `employee` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`emp_name` VARCHAR(100) NOT NULL DEFAULT '0',
	`birth_date` DATE NOT NULL,
	PRIMARY KEY (`id`)
)
ENGINE=InnoDB;

EmployeeDao.java

import java.io.Serializable;
import java.util.Date;

public class EmployeeDao implements Serializable {
  private static final long serialVersionUID = 1L;

  private Long id;
  private String empName;
  private Date birthDate;
  
  public Long getId() {
    return this.id;
  }
  public void setId(Long id) {
    this.id = id;
  }
  public String getEmpName() {
    return this.empName;
  }
  public void setEmpName(String empName) {
    this.empName = empName;
  }
  public Date getBirthDate() {
    return this.birthDate;
  }
  public void setBirthDate(Date birthDate) {
    this.birthDate = birthDate;
  }
}

EmployeeBeanProcessor.java

本当はアノテーションを使って、DBの項目名とJavaのプロパティをマッピングしたいがめんどいので...
import java.beans.PropertyDescriptor;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;

import org.apache.commons.dbutils.BeanProcessor;

public class EmployeeBeanProcessor extends BeanProcessor {
  
  @Override
  protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
          PropertyDescriptor[] props) throws SQLException {
      int columns = rsmd.getColumnCount();
      int columnToProperty[] = new int[columns + 1];
      Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
      for (int col = 1; col <= columns; col++) {
          // ★getColumnLabel()を使用することにより、別名(AS)でマッピングできる★
          String columnName = rsmd.getColumnLabel(col);
          for (int i = 0; i < props.length; i++) {
              if (equalsColumnProperty(columnName, props[i].getName())) {
                  columnToProperty[col] = i;
                  break;
              }
          }
      }
      return columnToProperty;
  }
  private boolean equalsColumnProperty(String colName, String propName) {
      return colName.replaceAll("_", "").equalsIgnoreCase(propName);
  }
}

DbConnector.java

import java.io.Closeable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.RowProcessor;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

public class DbConnector implements Closeable {
  private Connection connection = null;
  private QueryRunner queryRunner = null;

  @Override
  public void close() {
    try {
      if (this.connection != null && !this.connection.isClosed()) {
        DbUtils.closeQuietly(this.connection);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

  public void initialize(String dbUrl, String dbUserId, String dbPassword) throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    this.queryRunner = new QueryRunner();
    // DB接続
    this.connection = DriverManager.getConnection(dbUrl, dbUserId, dbPassword);
  }

  public void beginTransaction() throws SQLException {
    this.connection.setAutoCommit(false);
  }

  public void commit() {
    DbUtils.commitAndCloseQuietly(this.connection);
  }

  public void rollback() {
    DbUtils.rollbackAndCloseQuietly(this.connection);
  }

  /**
   * BeanListHandlerのサンプル
   */
  public <T> T selectUnique(Class<T> targetClass, String query, Object... parameters) throws SQLException {
    RowProcessor rp = new BasicRowProcessor(new EmployeeBeanProcessor());
    BeanHandler<T> beanHandler = new BeanHandler<T>(targetClass, rp);
    return (T) this.queryRunner.query(this.connection, query, beanHandler, parameters);
  }

  /**
   * BeanListHandlerのサンプル
   */
  public <T> List<T> selectList(Class<T> targetClass, String query, Object... parameters) throws SQLException {
    RowProcessor rp = new BasicRowProcessor(new EmployeeBeanProcessor());
    ResultSetHandler<List<T>> resultSetHandlers = new BeanListHandler<T>(targetClass, rp);
    return (List<T>) this.queryRunner.query(this.connection, query, resultSetHandlers, parameters);
  }

  /**
   * ScalarHandlerのサンプル
   */
  public Long selectLongValue(String query, Object... parameters) throws SQLException {
    ScalarHandler<Long> resultSetHandler = new ScalarHandler<Long>(1);
    return  this.queryRunner.query(this.connection, query, resultSetHandler, parameters);
  }

  /**
   * QueryRunner.updateのサンプル
   */
  public Long insert(String query, String... parameters) throws SQLException {
    PreparedStatement statement = this.connection.prepareStatement(query, java.sql.Statement.RETURN_GENERATED_KEYS);

    int index = 1;
    for (String parameter : parameters) {

      // FIXME
      if (parameter instanceof String) {
        statement.setString(index, parameter);
      } else {
        statement.setObject(index, parameter);
      }

      index++;
    }
    // 追加
    statement.executeUpdate();

    // auto-incrementの値取得
    Long returnValue = Long.MIN_VALUE;
    ResultSet rs = statement.getGeneratedKeys();
    if (rs.next()) {
      returnValue = rs.getLong(1);
    }
    return returnValue;
  }

  /**
   * QueryRunner.updateのサンプル
   */
  public void update(String query, Object... parameters) throws SQLException {
    this.queryRunner.update(connection, query, parameters);
  }
}

Main.java

実際の使用例
import java.util.List;

public class Main {

  public static void main(String[] args) {
    try (DbConnector dbConnector = new DbConnector()) {
      dbConnector.initialize("jdbc:mysql://localhost:3306/sampledb", "root", "password");

      try {
        // オートコミットを無効
        dbConnector.beginTransaction();

        // SELECTサンプル
        System.out.println("---登録データ---");
        List<EmployeeDao> people1 = dbConnector.selectList(EmployeeDao.class, "SELECT * FROM employee");
        print(people1);

        // INSERTサンプル
        String query = "INSERT INTO employee (emp_name, birth_date) VALUES (?, ?)";
        dbConnector.insert(query, "Coco", "2010/1/22");
        dbConnector.insert(query, "Andy", "1991/5/12");

        // UPDATEサンプル
        dbConnector.update("UPDATE employee SET emp_name = ? WHERE birth_date = ?", "Ken", "2000/11/21");

        // 更新結果確認
        System.out.println("---更新結果---");
        List<EmployeeDao> people2 = dbConnector.selectList(EmployeeDao.class, "SELECT * FROM employee");
        print(people2);

        // commit
        dbConnector.rollback();
      } catch (Exception ex) {
        dbConnector.rollback();
        ex.printStackTrace();
        throw ex;
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void print(List<EmployeeDao> employees) {
    for (EmployeeDao employee : employees) {
      System.out.println("************");
      System.out.println(employee.getId());
      System.out.println(employee.getEmpName());
      System.out.println(employee.getBirthDate());
      System.out.println("************");
    }
  }
}


関連記事

O/Rマッパー [2] ~Apache Commons DbUtils

http://blogs.yahoo.co.jp/dk521123/34079202.html

Apache Commons DbUtils でJoinしたデータを扱うには...

http://blogs.yahoo.co.jp/dk521123/36128075.html

Apache Commons DbUtils で、List<String> を返すようにするには...

http://blogs.yahoo.co.jp/dk521123/36274590.html

アノテーション ~ Annotation ~

http://blogs.yahoo.co.jp/dk521123/32740125.html