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.

Reply via email to