オレオレ日付型をつくろう(Doma 0.9.10)

日付のAPIJavaの中でもよく文句が言われるところですね。
独自のクラスを作るという解決策はありますが、O/Rマッパーによってはユーザー定義の型をDBにマッピングするのが大変だったりするので(Hibernateとか)それはそれで躊躇してしまうかもしれません。

Domaでは、ドメインクラスを使って値型を簡単にDBにマッピングできます。この機能を生かして、オレオレな日付型を作る方法を紹介します。手順は4つあります。

  1. publicなクラスに@Domainをつける
  2. @DomainのvalueType要素にはDBにマッピングする基本型を指定する
  3. publicなコンストラクタで@DomainのvalueType要素で指定したクラスを受ける(ドキュメントには非privateならOKとありますがpublicにしてください)
  4. publicなgetValueメソッドで@DomainのvalueType要素で指定したクラスを返す

あとは自由。まちがってもaptがすぐにまちがいを指摘してくれるので結構簡単にできますよ。
今回はstaticなファクトリメソッドで年月日を受け取る日付型を作ります。こんな感じです。valueTypeに指定しているのはjava.sql.Dateです(java.util.Dateはサポートしてません)。immutableにしておきます。

@Domain(valueType = Date.class)
public class MyDate {

    private final Date value;

    public MyDate(Date value) {
        if (value == null) {
            this.value = null;
        } else {
            this.value = new Date(value.getTime());
        }
    }

    public MyDate(long time) {
        this.value = new Date(time);
    }

    public Date getValue() {
        if (value == null) {
            return null;
        }
        return new Date(value.getTime());
    }

    public static MyDate of(int year, int month, int date) {
        if (year < 2000 || 2100 < year) {
            throw new IllegalArgumentException(String.valueOf(year));
        }
        if (month < 1 || 12 < month) {
            throw new IllegalArgumentException(String.valueOf(month));
        }
        if (date < 1 || 31 < date) {
            throw new IllegalArgumentException(String.valueOf(date));
        }
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month - 1);
        calendar.set(Calendar.DATE, date);
        return new MyDate(calendar.getTimeInMillis());
    }

    @Override
    public String toString() {
        if (value == null) {
            return null;
        }
        return value.toString();
    }

}

Hibernateのユーザー定義型とちがって永続化のコードが一切入っていないのがアピールポイントです。Hibernateのユーザー定義型はいろいろと実装する必要があって大変そう。http://docs.jboss.org/hibernate/stable/core/api/org/hibernate/usertype/UserType.html

使い方は他の値型と変わりません。普通にエンティティクラスに定義できます。

@Entity(naming = NamingType.SNAKE_UPPER_CASE)
public class Employee {
 String name;
 MyDate date;
 ...
}

Daoを使って更新してみます。

EmployeeDao dao = new EmployeeDaoImpl();
Employee employee = dao.selectById(1);
employee.setHiredate(MyDate.of(2009, 11, 24));
dao.update(employee);

ログもきれいに出ますよ。

update EMPLOYEE set HIREDATE = '2009-11-24', VERSION = 1 + 1 where EMPLOYEE_ID = 1 and VERSION = 1

今回はstaticなファクトリメソッドで年月日を受け取るメソッドしかありませんが、日付に関するいろんなメソッドをこのクラスに持ってくることができると思います。

一応デメリットを言っておくと、Webフレームワークとの相性の問題はあるかもしれません。たとえば、リクエストパラメータからの変換とか。しかし、たいがいのWebフレームワークはConverter的なクラスを拡張できると思うのでそのあたりでうまくStringからオレオレ日付型に変換できるんじゃないかなーと思います。