1

I'm trying to imagine how to use jOOQ with bridge tables. Suppose you have

CREATE TABLE TableA (
  id BIGSERIAL PRIMARY KEY
)

CREATE TABLE TableB (
  id BIGSERIAL PRIMARY KEY
)

CREATE TABLE TableBridge (
  id BIGSERIAL,
  table_a_id INTEGER NOT NULL,
  table_b_id INTEGER NOT NULL,
  CONSTRAINT tablea_pk_id PRIMARY KEY (table_a_id)
   REFERENCES TableA (id) MATCH SIMPLE,
  CONSTRAINT tableb_pk_id PRIMARY KEY (table_b_id)
   REFERENCES TableB (id) MATCH SIMPLE
)

When mapping this schema using jOOQ there will be three record classes, TableARecord, TableBRecord and TableBridgeRecord.

If I want to persist through an insert a record for TableA, should I simply first create and persist the TableB records, then persit rows for TableB and then manually add the TableBridge rows? Isn't there any way to automatically save also the rows in the bridge table?

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
mat_boy
  • 12,998
  • 22
  • 72
  • 116

1 Answers1

1

There are several ways to solve this kind of problem:

1. Do it with a "single" jOOQ statement (running three SQL statements)

The most idiomatic way to solve this kind of problem with standard jOOQ would be to write a single SQL statement that takes care of all three insertions in one go:

ctx.insertInto(TABLE_BRIDGE)
   .columns(TABLE_BRIDGE.TABLE_A_ID, TABLE_BRIDGE.TABLE_B_ID)
   .values(
        ctx.insertInto(TABLE_A)
           .columns(TABLE_A.VAL)
           .values(aVal)
           .returning(TABLE_A.ID)
           .fetchOne()
           .get(TABLE_A.ID),
        ctx.insertInto(TABLE_B)
           .columns(TABLE_B.VAL)
           .values(bVal)
           .returning(TABLE_B.ID)
           .fetchOne()
           .get(TABLE_B.ID)
   )
   .execute();

The above works with jOOQ 3.8. Quite possibly, future versions will remove some of the verbosity around returning() .. fetchOne() .. get().

2. Do it with a single SQL statement

I assume you're using PostgreSQL from your BIGSERIAL data type usage, so the following SQL statement might be an option to you as well:

WITH
  new_a(id) AS (INSERT INTO table_a (val) VALUES (:aVal) RETURNING id),
  new_b(id) AS (INSERT INTO table_b (val) VALUES (:bVal) RETURNING id)
INSERT INTO table_bridge (table_a_id, table_b_id)
SELECT new_a.id, new_b.id
FROM new_a, new_b

The above query is currently not supported entirely via jOOQ 3.8 API, but you can work around the jOOQ API's limitations by using some plain SQL:

ctx.execute(
  "WITH "
+ "  new_a(id) AS ({0}), "
+ "  new_b(id) AS ({1}) "
+ "{2}",

  // {0}
  insertInto(TABLE_A)
  .columns(TABLE_A.VAL)
  .values(aVal)
  .returning(TABLE_A.ID),

  // {1}
  insertInto(TABLE_B)
  .columns(TABLE_B.VAL)
  .values(bVal)
  .returning(TABLE_B.ID),

  // {2}
  insertInto(TABLE_BRIDGE)
  .columns(TABLE_BRIDGE.TABLE_A_ID, TABLE_BRIDGE.TABLE_B_ID)
  .select(
      select(field("new_a.id", Long.class), field("new_b.id", Long.class))
      .from("new_a, new_b")
   )
);

Clearly also here, there will be improvements in future jOOQ APIs.

3. Do it with UpdatableRecords

In this particular simple case, you could get away simply by calling:

TableARecord a = ctx.newRecord(TABLE_A);
a.setVal(aVal);
a.store();

TableBRecord b = ctx.newRecord(TABLE_B);
b.setVal(bVal);
b.store();

TableBridgeRecord bridge = ctx.newRecord(TABLE_BRIDGE);
bridge.setTableAId(a.getId());
bridge.setTableBId(b.getId());
bridge.store();
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Case 3 is more "object oriented", but is it particularly unefficient compared to case 1 and 2? – mat_boy Dec 01 '16 at 19:50
  • @mat_boy: Case 1 and 3 are equivalent. Case 2 might be a bit more efficient because there's only a single server round trip - but I'm actually not sure how `INSERT` statements inside of CTE (`WITH` clause) are optimised... – Lukas Eder Dec 01 '16 at 20:49