I am retrieving a record from a query with multiple join clauses, making it composite. The record has about 20 columns, so I am looking for an efficient way to access the data. I will only look at the data and not modify anything or commit back to the database.
I looked at two short, readable options that jOOQ provides:
get(Field<?>)
from the Record
instance of fetchOne()
,fromMap(Map<String, ?>)
with a map from fetchOneMap()
, and using the generated getters.To try and find out how efficient these options are, I looked at how these methods are implemented. If I looked correctly, get
is implemented linearly (via field0(Field<?>, FieldOrIndex<U>)
), which would make accessing the n
fields get a time complexity of O(n^2).
I figured that perhaps, fromMap(Map<String, ?>)
would be efficient to initialize schema-generated records instead, so the generated getters can be used. However, fromMap
eventually calls from(Record)
which loops over all fields while (inside the loop) indirectly calling field0
(that was linear), making fromMap
have a time complexity of O(n^2) as well. Of course, retrieving the n
columns would now have a complexity of O(n), but the earlier initialization has caused a bottleneck.
In principle, my question reads:
Is there a way to access column data of a composite record by simple methods (single parameter like get(Field<?>)
or the generated getters), in a somewhat constant manner (thus in total linearly)? Or am I left with using the Map<String, Object>
from fetchOneMap
, sacrificing some readability due to manually casting?
Thanks in advance!
The O(N)
read time complexity of the common Record.get(Field<T>)
method is unfortunate, of course. Much faster access can be achieved when accessing data by index, e.g. via:
Record.get(int)
Record20.value3()
, etc.
Records.mapping(Function20)
, etc.
DefaultRecordMapper
to map data into a DTO / POJO using reflection.
Configuration
instance, if you're sharing that among sessions, so the bulk overhead happens only once per query typeTableRecord
types
A hypothetical ideal solution would be:
record
type, and then uses index-based access to map between jOOQ records and Java records. This would work like an F# type provider. If that's ever possible in Java / jOOQ, this will be the default option.So, as you can see, it's a tradeoff. You don't have to forego type safety when accessing values by index (up to degree 22), but just as with JDBC, index based access is error prone when you add columns to the beginning of a row, when all other columns shift by 1.
While a jOOQ Result
is not an optimal data structure for processing data in-memory, all benchmarks in the past have shown that the overhead produced by the actual SQL query is orders of magnitudes more significant than the overhead caused by this record access, even when running trivial queries against an in-memory H2 database. Alternative internal representations using a Map<Field<?>, ?>
didn't perform much better because of the internal memory overhead of a Map
compared to an Object[]
, as well as the increased CPU overhead - at least for small N
. It would probably fare better for e.g. 200 columns, but still, a 200 column query will produce orders of magnitudes more overhead on the server side than your mapping.
If you can measure a significant overhead, e.g. using a profiler, then the fastest access is always by index, e.g. using Record.get(int)
or Record.get(int, Class<T>)
. Chances are, you won't measure anything significant, though.