On Monday, 18 September 2023 at 14:01:18 UTC+2 Lukas Eder wrote:
On Sat, Sep 16, 2023 at 2:10 AM Yafl Wabei <[email protected]> wrote:
Hi again!
// 3. Fetching into the TableRecord
// WORKS NOT! VALUES ARE NULL
TestRecord fetchOneInto_TableRecord =
ctx.select(TEST).from(TEST).where(TEST.ID.eq(id)).fetchOneInto(TestRecord.class);
This is record-to-record mapping, which is governed by field equivalence.
The nested Record1<TestRecord> record doesn't have any corresponding fields
with TestRecord
I do get that. But if DefaultRecordMapper would treat TestRecord just as a
custom type and then use the same algorithm as above for the POJO (or any
other custom type), would it then work? That's just not what happens,
because the DefaultRecordMapper always field-equivalence for
Record-to-Record mapping, right?
But it doesn't treat TestRecord as a custom type, otherwise, it couldn't
transfer record state (such as changed flags, the fetched flag, original
values, etc.).
I see, that makes sense! Now I also found the relevant code sections in the
jooq source code. I still wonder if it would somehow be possible for the
DefaultRecordMapper make the behaviour similar to the other case. E.g.
would it be possible to determine that (in the discussed case) the usual
record mapping doesn't fit for mapping Record1<TestRecord> to
TestRecord.class and instead try to unnest Record1 and map the result to
the target? I.e. in simplified pseudo code for the general case:
// "Parameters":
Record<?> row;
Class<E> type;
if (AbstractRecord.class.isAssignableFrom(type)) {
if (row is Record1 && "no sensible mapping from row to E" possible) {
// <-- Is it possible to determine this?
Object v = row.get(0);
// Try to map v to E, by (recursively) using the
DefaultRecordMapper again
map(v, type);
} else {
// Use default RecordToRecordMapper;
}
But I guess records don't have enough type information to determine if they
"match" or not?! Even if, I just made this up and have no clue about the
consequences for all other cases which are not like in my example.
It would be a fun exercise to specify fully and formally what it means for
there to be "no sensible mapping" (including any potential future "sensible
mapping" that we may still want to add). Think about things like:
https://github.com/jOOQ/jOOQ/issues/11148. Though, I'd rather spend my time
on more pressing features, currently. I believe that since jOOQ 3.15's
various changes to add more ad-hoc conversion and type safety to mapping
(including nested collections), the reflective DefaultRecordMapper might
become less popular.
Ah so the order (implemented in the DefaultRecordMapper) of which Mapper to
choose was, in fact, the other way around before! And that caused #11148.
Now I see where the order of the conditions in the JavaDoc comes from and
why it's not up-to-date anymore :)
When I came up with this idea up there, it was more a "would this
theoretically be possible" question than a desperate wish for an
implementation. I do agree that there are more important features to spend
time on.
As to the new type-safe mappings: I'm always happy about more type safety
and will definitely use them when appropriate. To me the reflective mapping
has some advantage though, because sometimes the type-safe mapper might be
too verbose in comparison. Also, when the target object (e.g. a Java 16
record) has 7 String fields, using a constructor method reference might
(more easily) lead to interchanged values just by mistakenly confusing the
order of columns in the query-select.
More mistakes can happen with the reflective mapping than the type safe
one. For added type safety, you could use SQL DOMAIN types.
After some thought and some more usage, I do agree.
The question of this thread is one good example for that were it silently
went wrong even without a runtime error (in the jooq query– the null values
in the resulting record produced a runtime exception in my business logic
code).
I also see that if I want to add a new column to a query, it can happen
that I add it to the query, but forget to add it in the Dto that I map
into, or the other way around. Then:
With reflection, this will go wrong silently again.
With a type safe constructor reference mapping, both cases would produce a
compile error.
A type safe mapping using a custom mapper method where I call setters on a
Dto is somewhere in between. Adding it to the query will produce a compile
error on using my mapper method (argument RecordN instead of RecordN+1). I
can still fix the mapper signature and might forget to call the setter on
the Dto and this won't produce a compile error. The other way around, if I
add the column to the Dto and set it in the mapper (but forget to change
the mapper argument from RecordN to RecordN+1), using record.get(N,
Type.class) or record.get(TABLE.FIELD) will at least produce a runtime
error in the mapper.
So it's not as good as the constructor reference, but at least there's less
mistakes than with reflection.
I guess there are more examples and cases how reflection can go wrong
silently.
Since I realized this, I'm using the reflection mapping less and the type
safe ones more and it does feel better to do it this way.
I have no more questions regarding this topic. I gained a lot of
understanding from this conversation. Thanks very much for all the advice,
Lukas!
--
You received this message because you are subscribed to the Google Groups "jOOQ
User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/jooq-user/20fd2e1c-b922-415a-8af9-9d3aa0a31341n%40googlegroups.com.