APサーバーが複数存在していて時刻の差が気になったり、宗教上の理由などにより、
テーブルの時刻列を現在日時で更新する際にDB側の現在日時(postgresならcurrent_timestampとか)を使いたいケースはよくあると思います。
DB側もクラスタリングされていて時刻同期機能的な奴がどうとかは一旦全部無視すると普通に
UPDATE test SET time = current_timestamp WHERE id = @id;
みたいな感じにすればいいだけですね、簡単です。
でもそれORMでどうやるの?
ORMを使用(可能な限り生クエリは実行しない前提)している場合、一気に難易度が上がったりします。
例えば、play framework(ORM:EBean)の場合、以下のようなセッションを管理するテーブルがあるとします。
@Entity
@Table(name = "sessions")
@Getter
@Setter
public class SessionEntity implements Session{
@Id
@Column(name = "session_id")
@NotNull
private Long sessionId;
@Column(name = "start_time")
@NotNull
private Timestamp startTime;
@Column(name = "end_time")
private Timestamp endTime;
}
大体以下のような感じで、セッションの開始時、終了時にレコードの作成、更新をするわけですが、
Entityに値をセットして作成(更新)をするという形になるので、DB側の時刻で処理する方法が良く分かりません。
public class SessionController extends Controller {
public static Result startSession(){
// セッションの開始(レコードの作成)
SessionEntity session = new SessionEntity();
session.setSessionId(1L);
session.setStartTime(new Timestamp(System.currentTimeMillis()));
Ebean.create(session);
}
public static Result endSession(){
// セッションの終了(レコードの更新)
session.setEndTime(new Timestamp(System.currentTimeMillis()));
Ebean.update(session);
}
}
グーグル先生と小一時間格闘しましたが、
- Entityの時刻フィールドにあるアノテーションを付けると作成時にDB側の時刻でレコードを作成する
- Entityの時刻フィールドにあるアノテーションを付けると更新時に毎回DB側の時刻で更新する
ということはできそうだったので、セッション開始時間の方は解決できるのですが、
セッション終了時間を任意のタイミングでDB側の時刻で更新する方法は見つかりませんでした。(生クエリを実行する以外の方法で)
ならばトリガだ
結果どうしたかと言うと、アプリケーションコードはほぼそのままで、「トリガで更新し直す」ことにしました。
CREATE FUNCTION reupdate_endtime()
RETURNS trigger AS
$BODY$
BEGIN
NEW.time := current_timestamp;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION reupdate_endtime()
OWNER TO postgres;
CREATE TRIGGER reupdate_endtime
BEFORE UPDATE OF "end_time"
ON sessions
FOR EACH ROW
EXECUTE PROCEDURE reupdate_endtime();
ただ、この方法だとEntityにトリガで更新された時刻がセットされないので、update()後に再取得処理が動くようにしておく必要がありました。
最後に
ORM使うの難しい