jOOQ で audit フィールドを自動的に更新する方法
jOOQ 便利ですよね。 Java の ORM としては定番と言えるライブラリではありますが、
created_at
や updated_at
といった audit カラムを特に手動でセットせずとも、
自動的に更新したいというありがちなニーズに対応する方法が簡単には見つけられない気がします。
というわけで今回はその方法を紹介します。 StackOverflow に書いてあるんですけどね。
方法は 4 つあるようです。
- SPI (Service Provider Interface) の
RecordListener
を使って audit フィールドをセットする - SPI の
VisitListener
を使って SQL を改変することで audit フィールドを更新する - jOOQ 側ではなくデータベース側の Trigger を使って audit フィールドを更新する
- jOOQ 3.10 を待つ
先に 2. 3. 4. の方法を確認していきます。
まず 2. の VisitListener
を使う場合、公式のサンプルコードを参考に実装するわけですが、
これがまたなかなか厄介な実装になります。
手元で実装コードを書いてみましたが、力技になったりするのと Clause
が @Deprecated
になっていたりするのを見て
これはつらいなと思って途中でやめましたw
GitHub に issue があるので要望がある人はそこで貢献していきましょうって感じですね。
続いて 3. ですが、こちらは jOOQ のリポジトリにサンプルコードを載せてくれている方がいたので参考にしましょう。
そして 4. ですが、現在最新の jOOQ はすでに 3.11 です!!!残念ながらまだ実装されていません。 メインの issue はこちらですが難航しているようですね...
というわけで、一番手軽な 1 つめの方法で実装したサンプルコードを GitHub にあげました。
方法 1. のメリットは手軽さで、デメリットは jOOQ の呼び出し方法によっては RecordListener が invoke されないという点です。 StackOverflow にあるように所定のメソッドが呼ばれた時のみ、という制限があるんですね。
さてサンプルコードを簡単に説明しますと
public class AuditRecordListener extends DefaultRecordListener { @Override public void insertStart(RecordContext ctx) { ... } @Override public void updateStart(RecordContext ctx) { ... } ... }
DefaultRecordListener
を継承して insertStart
と updateStart
をオーバーライドし、
created_at
と updated_at
のフィールドに現在時刻をセットしてあげます。
jooq-spring-audit-example/Auditable.java at v0.0.1 · innossh/jooq-spring-audit-example · GitHub
... public interface Auditable<T> { public LocalDateTime getCreatedAt(); public T setCreatedAt(LocalDateTime value); public LocalDateTime getUpdatedAt(); public T setUpdatedAt(LocalDateTime value); }
... public class AuditGeneratorStrategy extends DefaultGeneratorStrategy { @Override public List<String> getJavaClassImplements(Definition definition, Mode mode) { if (RECORD.equals(mode)) { return Arrays.asList(Auditable.class.getName()); } return new ArrayList<>(); } }
jooq-spring-audit-example/build.gradle at v0.0.1 · innossh/jooq-spring-audit-example · GitHub
... generator { name = 'org.jooq.codegen.DefaultGenerator' strategy { name = 'innossh.jooq.spring.audit.example.db.codegen.AuditGeneratorStrategy' } ... } ...
サンプルでは上記のように Auditable
インターフェースを作り、 jOOQ で Record
クラスを generate する時の implements に指定していますが、
そのようなインターフェースを介さずともオーバーライドしたメソッドの中で、
単純にレコードの全フィールド名の String を created_at
と updated_at
に比較してセットする、でも問題ないです。
@Service public class DefaultUserService implements UserService { ... @Override public void createUser(User user) { userDao.insert(user); } ... }
最後は DAO で insert とか update すれば勝手に現在時刻で更新してくれます。
この時に DSLContext
で insertInto
とか呼ぶと RecordListener は invoke されないので、
DSLContext
を直接使用してクエリを構築する場合は自前で現在時刻をセットしましょう。悲しみ。
しかし jOOQ の generate はとても便利ですね。また公式でアップデートがあったら触ってみようと思います。