jOOQ Record API: Forms, Creation, and Practical Usage
Record shapes
A Record in jOOQ is a typed, positional container that pairs a sequence of Field definitions with their corresponding values. It behaves like a column-to-value tuple stored in order. Generated table-specific records expose getters and setters for each column, but internally a Record stores values in arrays; direct JSON serialization/deserialization is not supported without custom mapping.
Table-specific records
For each database table, the code generator produces a TableRecord subtype. When a table has a primary key, its record type extends UpdatableRecordImpl, enabling insert, update, and delete operations through instance methods. Query results retrieved via jOOQ are materialized as these table-specific record instanecs.
Example (table with 6 columns):
class S1UserRecord extends UpdatableRecordImpl<S1UserRecord>
implements Record6<Integer, String, String, String, Timestamp, Timestamp> {
}
Example (table with > 22 columns):
class S4ColumenGt22Record extends UpdatableRecordImpl<S4ColumenGt22Record> {
}
UDT records
Databases that support user-defined types (e.g., Oracle) map those types to UDTRecord implementations.
Arity-based records
jOOQ exposes generic Record1..Record22 interfaces to operate on a fixed number of values and types. Generated table records implement RecordN when the number of columns is ≤ 22, aliginng the generic types and order with the table’s columns.
Sketch of the RecordN API:
interface RecordN<T1, /* ... */ TN> {
// Field accessors
Field<T1> field1();
/* ... */
Field<TN> fieldN();
// Value accessors
T1 value1();
/* ... */
TN valueN();
// Value mutators
RecordN<T1, /*...*/ TN> value1(T1 v1);
/* ... */
RecordN<T1, /*...*/ TN> valueN(TN vN);
}
Creating records
new
Direct instantiation creates a detached record with no configured connection. You can still pass it to DSLContext statements, but record.insert()/update()/delete() will fail when no configuration is attached.
S1UserRecord r = new S1UserRecord();
r.setUsername("u_new");
r.setEmail("u_new@example.com");
// Throws when no Configuration is attached
r.insert();
// Use the record as a value container in DSLContext statements
int inserted = dsl.insertInto(S1_USER)
.set(r)
.execute();
Integer generatedId = dsl.insertInto(S1_USER)
.set(r)
.returning(S1_USER.ID)
.fetchOne()
.getId();
DSLContext.newRecord
Records created via DSLContext are attached to the same configuration and can call insert/update/delete directly.
S1UserRecord r = dsl.newRecord(S1_USER);
r.setUsername("newRecordUser");
r.setEmail("nr@example.com");
r.insert();
fetch/fetchInto
Query methods return attached record instances ready for further persistence operations.
S1UserRecord r1 = dsl.selectFrom(S1_USER)
.where(S1_USER.ID.eq(1))
.fetchOne();
r1.setEmail("updated+1@example.com");
int rows1 = r1.update();
S1UserRecord r2 = dsl.select()
.from(S1_USER)
.where(S1_USER.ID.eq(1))
.fetchOneInto(S1UserRecord.class);
r2.setEmail("updated+2@example.com");
int rows2 = r2.update();
Persistence APIs
insert
- insert(): persists all fields that were set
- insert(Field<?>... fields): persists the specified subset of set fields
- insert(Collection> fields): same as above using a collection
Only fields explicitly set on the record are rendered into the SQL statement.
S1UserRecord r = dsl.newRecord(S1_USER);
r.setUsername("insert_user");
r.setEmail("insert@example.com");
r.insert();
r = dsl.newRecord(S1_USER);
r.setUsername("subset_user");
r.setEmail("ignored@example.com");
r.insert(S1_USER.USERNAME, S1_USER.ADDRESS); // only USERNAME persisted here
update
- update(): updates all changed fields
- update(Field<?>... fields): updates a specified subset
- update(Collection> fields): same with a collection
Only changed fields are updated.
S1UserRecord r = dsl.selectFrom(S1_USER)
.where(S1_USER.ID.eq(1))
.fetchSingle();
r.setEmail(null);
r.setUsername("alice");
r.update(S1_USER.USERNAME, S1_USER.ADDRESS);
delete
Deletes by primary key.
S1UserRecord r = dsl.newRecord(S1_USER);
r.setId(42);
r.delete();
S1UserRecord rNoKey = dsl.newRecord(S1_USER);
rNoKey.delete(); // WHERE id IS NULL
Data access APIs
get
- get(Field<T>): read a field value
- get(Field<T>, Class<U>): read and convert to a target type
Record any = dsl.select().from(S1_USER).limit(1).fetchOne();
Integer id = any.get(S1_USER.ID);
Long createdAsLong = any.get(S1_USER.CREATE_TIME, Long.class);
Generated table records also expose typed getters:
S1UserRecord ur = dsl.selectFrom(S1_USER).fetchAny();
Integer id2 = ur.getId();
Timestamp created = ur.getCreateTime();
RecordN value accessors provide positional reads when working with fixed arity:
Record3<Integer, String, Timestamp> triple = dsl
.select(S1_USER.ID, S1_USER.USERNAME, S1_USER.CREATE_TIME)
.from(S1_USER)
.fetchAny();
Integer id3 = triple.value1();
String uname = triple.value2();
Timestamp created3 = triple.value3();
set
- set(Field<T>, T): assign a value programmatically
S1UserRecord r = dsl.newRecord(S1_USER);
r.set(S1_USER.ID, 7);
r.set(S1_USER.USERNAME, "prog_user");
r.update();
Generated setters mirror the table columns:
S1UserRecord r2 = dsl.newRecord(S1_USER);
r2.setId(7);
r2.setUsername("setter_user");
r2.update();
changed
Toggle whether a field is considered changed. Useful to avoid updating or inserting a particular column.
S1UserRecord r = dsl.newRecord(S1_USER);
r.setId(1);
r.setUsername("kept");
r.setEmail(null);
r.changed(S1_USER.EMAIL, false); // EMAIL will not be persisted
r.update();
reset
Equivalent to changed(field, false), clearing a field’s changed flag.
S1UserRecord r = dsl.newRecord(S1_USER);
r.setId(1);
r.setUsername("kept2");
r.setEmail(null);
r.reset(S1_USER.EMAIL);
r.update();
Object mapping APIs
Record supports converting from/to various representations via from/into methods.
from
Populate a record from objects, maps, or arrays.
S1UserPojo p = new S1UserPojo();
p.setUsername("p_user");
p.setAddress("p_addr");
S1UserRecord r1 = dsl.newRecord(S1_USER);
r1.from(p);
Map<String, Object> m = new HashMap<>();
m.put("username", "m_user");
m.put("address", "m_addr");
S1UserRecord r2 = dsl.newRecord(S1_USER);
r2.fromMap(m);
Object[] arr = new Object[] { "a_user", "a_addr" };
S1UserRecord r3 = dsl.newRecord(S1_USER);
r3.fromArray(arr, S1_USER.USERNAME, S1_USER.ADDRESS);
into
Convert a record into another type or projection.
S1UserRecord rec = dsl.selectFrom(S1_USER)
.where(S1_USER.ID.eq(1))
.fetchOne();
S1UserPojo pojo = rec.into(S1UserPojo.class);
Record2<Integer, String> idAndName = rec.into(S1_USER.ID, S1_USER.USERNAME);
Integer onlyId = idAndName.value1();
Object[] values = rec.intoArray();
Integer idFromArray = (Integer) values[0];
Map<String, Object> map = rec.intoMap();
Integer idFromMap = (Integer) map.get("id");
ResultSet rs = rec.intoResultSet();