jOOQのCRUD機能(ORマッピング)

2019年7月17日

jOOQまとめがあるので参照されたい

CRUDとは、Create/Read/Update/Deleteのことなのだが、ここではORマッピングのことだと思えば良い。つまりは、Java側のオブジェクトとテーブルレコードをマッピングするということだ。

これについては、jOOQのマニュアルがわかりやすいかと思うので、このマニュアルに沿って見ていくが、こちらでの勝手な解釈はあるので、その旨注意されたい。

プライマリキーが重要

正規化データベースでは、各レコードはプライマリーキーを特定され、これはレコードの生涯を通じて変更されない。通常は、番号ジェネレータによって自動的に付番される。jOOQのCRUD操作はこのようなデータベースを前提としている。

例えば、以下のようなものが理想なのだろう。レコードIDが必ず存在し、自動付番され、他と重複することはなく、削除後に同じ番号が再利用されることはない。

create table tbl_admin (
  adm_id bigint not null auto_increment,
  adm_email varchar(128) not null
  admin_password varchar(80) not null
);

コードジェネレータの使用

コードジェネレータを使用してデータベースからそれを表すJavaソースを作成した場合には、プライマリキーを持つすべてのテーブルについてorg.jooq.UpdatableRecordの下位クラスが作成される。これらのクラスに、jOOQのCRUDを実現する機能が含まれている。

つまり、レコードをDBから取得すると、そのレコードオブジェクトは、それを取得したコネクションにアタッチされている。そして、オブジェクトについて以下の操作を行うと、同じコネクションが使用される。

// レコードをDBから再取得する
void refresh() throws DataAccessException;

// DBに格納する(新規はinsert、更新はupdate)
int store() throws DataAccessException;

// レコードをDBから削除する
int delete() throws DataAccessException;

Store

レコードをstoreするとINSERTかUPDATEが実行される。一般に新たなレコードはINSERTされ、DBから取得したレコードはUPDATEされる。

// 新たなレコードを作成する
BookRecord book1 = create.newRecord(BOOK);

// INSERTする「INSERT INTO BOOK (TITLE) VALUES ('1984')」;
book1.setTitle("1984");
book1.store();

// 更新する「UPDATE BOOK SET PUBLISHED_IN = 1984 WHERE ID = [id]」
book1.setPublishedIn(1948);
book1.store();

// おそらくは自動生成されているレコードIDを取得する
Integer id = book1.getId();

// 別のBOOKのレコードを取得する
BookRecord book2 = create.fetchOne(BOOK, BOOK.ID.eq(id));

// 更新する「UPDATE BOOK SET TITLE = 'Animal Farm' WHERE ID = [id]」
book2.setTitle("Animal Farm");
book2.store();
  • jOOQは、変更された値だけをINSERT文やUPDATE文に設定する。これにより、INSERTのときにはデフォルト値が適用される。
  • INSERTのときにはjOOQはDBが生成したいかなるキー(ID値)もレコードに入れようと試みる。
    ※つまり、DBが生成したキーがDB上のレコードに自動格納されるだけではなく、Javaオブジェクトにも格納されるという意味だろう。
  • ID値をロードするだけではなく、store()はまたレコード全体をリフレッシュするようにも構成できる。
    ※どういう場合に必要なのかわからないが、何かしらID値以外が勝手に変更される場合に、それらを再度Java側に読み込む機能らしい。
  • 楽観的ロックを行っている場合は、読み込みから書き込みの間に変更されていればエラーになる。

Delete

以下のように削除する。

// Get a previously inserted book
BookRecord book = create.fetchOne(BOOK, BOOK.ID.eq(5));

// Delete the book
book.delete();

Refresh

プライマリーキー以外の値をDB上の最新の値にする。これは楽観的ロックを行っている場合に特に有用。

// Fetch an updatable record from the database
BookRecord book = create.fetchOne(BOOK, BOOK.ID.eq(5));

// Refresh the record
book.refresh();

CRUDとSelect文

通常のSELECTと組み合わせてもよい。次は特定の条件に一致したレコードを削除する例。

// Loop over records returned from a SELECT statement
for (BookRecord book : create.fetch(BOOK, BOOK.PUBLISHED_IN.eq(1948))) {

  // Perform actions on BookRecords depending on some conditions
  if ("Orwell".equals(book.fetchParent(Keys.FK_BOOK_AUTHOR).getLastName())) {
    book.delete();
  }
}