SQL transaction

jooqx simplify supports SQL transaction by using Pool client (e.g: JDBCPool, PgPool, etc…​) to borrows a connection from the pool, begins the transaction and releases the connection to the pool when the transaction ends.

Transaction

Transaction success

jooqx.transaction().run(tx -> {
    InsertResultStep<BooksRecord> q1 = tx.dsl()
                                         .insertInto(Tables.BOOKS, Tables.BOOKS.ID, Tables.BOOKS.TITLE)
                                         .values(Arrays.asList(DSL.defaultValue(Tables.BOOKS.ID), "hello guys"))
                                         .returning();
    InsertResultStep<BooksRecord> q2 = tx.dsl()
                                         .insertInto(Tables.BOOKS, Tables.BOOKS.ID, Tables.BOOKS.TITLE)
                                         .values(Arrays.asList(DSL.defaultValue(Tables.BOOKS.ID), "It's jooqx"))
                                         .returning();
    SelectOne<BooksRecord> selectOne = DSLAdapter.fetchOne(Tables.BOOKS);
    // Avoid using the scope from outside the transaction:
    // jooqx.execute(...);

    // ...but using context within the transaction scope:
    return tx.execute(q1, selectOne).flatMap(r1 -> tx.execute(q2, selectOne).map(r2 -> Arrays.asList(r1, r2)));
}).onSuccess(books -> {
    assert Objects.equals(books.toString(), """
        [+----+------------+
        |  id|title      |
        +----+-----------+
        |  *1|*hello guys|
        +----+-----------+
        , +----+-----------+
        |  id|title      |
        +----+-----------+
        |  *2|*It's jooqx|
        +----+-----------+
        ]
         """);
}).onFailure(Throwable::printStackTrace);

Handle rollback transaction

jooqx.transaction().run(tx -> {
    final Books table = Tables.BOOKS;
    final SelectOne<BooksRecord> selectOne = DSLAdapter.fetchOne(table);
    return tx.execute(dsl -> dsl.update(table)
                                .set(DSL.row(table.TITLE), DSL.row("something"))
                                .where(table.ID.eq(1))
                                .returning(), selectOne)                                            (1)
             .flatMap(r1 -> tx.execute(dsl -> dsl.update(table)
                                                 .set(DSL.row(table.TITLE), DSL.row((String) null))
                                                 .where(table.ID.eq(2))
                                                 .returning(), selectOne)                           (2)
                              .map(r2 -> Arrays.asList(r1, r2)));
}).recover(cause -> {
    assert cause instanceof DataAccessException;                                                    (3)
    assert Objects.equals(((DataAccessException) cause).sqlStateClass(),
                          SQLStateClass.C23_INTEGRITY_CONSTRAINT_VIOLATION);
    return Future.succeededFuture(Collections.emptyList());                                         (4)
});
1 First update is ok
2 Second update will be failed because of table constraint
3 Assert transaction exception
4 You can handle transaction rollback in here

Session

The different between transaction and session is:

  • transaction rollback if any error

  • session still continue executing the sequence SQL statements regardless if any SQL statement has an error.

final Authors tbl = Tables.AUTHORS;
jooqx.session()
     .perform(session -> {
         AuthorsRecord i1 = new AuthorsRecord().setName("n1").setCountry("AT");
         AuthorsRecord i2 = new AuthorsRecord().setName("n2");
         SelectOne<AuthorsRecord> selectOne = DSLAdapter.fetchOne(tbl);

         // Avoid using the scope from outside the session:
         // jooqx.execute(...);

         // ...but using context within the session scope:
         return session.execute(dsl -> dsl.insertInto(tbl).set(i1).returning(), selectOne)
                       .flatMap(r1 -> session.execute(dsl -> dsl.insertInto(tbl).set(i2).returning(), selectOne)
                                             .map(r2 -> Arrays.asList(r1, r2)));
     })
     .onFailure(t -> {
         assert t instanceof DataAccessException;
         assert t.getMessage().contains("null value in column \"country\" violates not-null constraint");
     })
     .eventually(unused -> jooqx.fetchExists(dsl -> dsl.selectFrom(tbl).where(tbl.NAME.eq("n1")))
                                .onSuccess(isExisted -> { assert isExisted; })
                                .onFailure(Throwable::printStackTrace));

Block

jooqx.block(dsl -> BlockQuery.create()
                             .add(dsl.selectFrom(Tables.AUTHORS).limit(2), DSLAdapter.fetchMany(Tables.AUTHORS))
                             .add(dsl.selectFrom(Tables.BOOKS).limit(2), DSLAdapter.fetchMany(Tables.BOOKS)))
     .onSuccess(blockResult -> {
         assert blockResult.size() == 2;

         final List<AuthorsRecord> authors = blockResult.get(0);
         assert Objects.equals(authors.toString(), """
             [+----+--------------+-------+
             |  id|name          |country|
             +----+--------------+-------+
             |  *1|*J.D. Salinger|*USA   |
             +----+--------------+-------+
             , +----+---------------------+-------+
             |  id|name                 |country|
             +----+---------------------+-------+
             |  *2|*F. Scott. Fitzgerald|*USA   |
             +----+---------------------+-------+
             ]
              """);

         final List<BooksRecord> books = blockResult.get(1);
         assert Objects.equals(books.toString(), """
             [+----+-----------------------+
             |  id|title                  |
             +----+-----------------------+
             |  *1|*The Catcher in the Rye|
             +----+-----------------------+
             , +----+-------------+
             |  id|title        |
             +----+-------------+
             |  *2|*Nine Stories|
             +----+-------------+
             ]
              """);
     })
     .onFailure(Throwable::printStackTrace);