Home > Mobile >  JOOQ wont call DTO setter during custom data type conversion
JOOQ wont call DTO setter during custom data type conversion

Time:09-29

I am using the JOOQ library in order to fetch the result from a select query into my custom DTO class. Because my Custom DTO class has an ENUM Type field mapped as an Integer Column in my database I am using a custom data type converter. The query I perform is just a basic select query:

public TestSuiteDto findByAuditTestSuiteId(Integer auditTestSuiteId) {

    TestSuiteJTable ts = TestSuiteJTable.TEST_SUITE;
    AuditTestSuiteJTable ats = AuditTestSuiteJTable.AUDIT_TEST_SUITE;

    List<TestSuiteDto> result = dsl.select(
                    ts.ID,
                    ts.DESCRIPTION,
                    ts.PROFILE_TYPE_ID,
                    ts.CREATED,
                    ts.ACTIVE,
                    ts.DEPRECATION,
                    ts.FEEDBACK_QUESTIONNAIRE_ID)
                .from(ts
                    .join(ats).on(ts.ID.eq(ats.TEST_SUITE_ID)))
                .where(ats.ID.eq(auditTestSuiteId))
                .fetchInto(TestSuiteDto.class);
    
    //do some stuff before returning

    return result.get(0); 
}

My custom DTO class looks like this

@Data
public class TestSuiteDto {

    private Integer id;
    private String description;
    private ProfileType profileType;
    private LocalDateTime created;
    private boolean active;
    private LocalDateTime deprecation;
    private Integer feedbackQuestionnaireId;
}

The problem is that during the fetching process the SETTER of the DTO class is never triggered for the ENUM type field e.g. profileType even though I have configured a custom data type converter:

@Slf4j
public class ProfileTypeConverter implements Converter<Integer, ProfileType> {

    @Override
    public ProfileType from(Integer databaseObject) {
        log.info("ProfileTypeConverter.from {} -> {}", databaseObject, ProfileType.getFromId(databaseObject));
        return ProfileType.getFromId(databaseObject);
    }

    @Override
    public Integer to(ProfileType userObject) {
        log.info("ProfileTypeConverter.to");
        return userObject.getId();
    }

    @Override
    public Class<Integer> fromType() {
        log.info("ProfileTypeConverter.fromType");
        return Integer.class;
    }

    @Override
    public Class<ProfileType> toType() {
        log.info("ProfileTypeConverter.toType");
        return ProfileType.class;
    }
}

I have added some logs just to check if the converter is triggered at all and I see that the converter is triggered as expected (from method of the Converter class is called during the execution of the JOOQ SQL query). I have also delombok my DTO class in order to add logs in SETTERs and GETTERs and see if those are also properly called. I found out that all the SETTERs are properly called except for the profileType one. Because of that when I retrieve the DTO from the result list the value of the profileType field is null. The column in my database (mysql) that maps to the profileType ENUM is called PROFILE_TYPE_ID and it is of type Integer. I have also configured a forcedType in the pom.xml following the examples on JOOQ documentation webpage.

<forcedType>
   <includeExpression>${jdbc.database}.TEST_SUITE.PROFILE_TYPE_ID</includeExpression>
   <userType>mypackage.type.ProfileType</userType>
   <converter>mypackage.converter.ProfileTypeConverter</converter>
</forcedType>

and this is how I have configured the ProfileType Field in pom.xml

<field>
    <expression>${jdbc.database}.TEST_SUITE.PROFILE_TYPE_ID</expression>
    <fieldIdentifier>
        <expression>PROFILE_TYPE_ID</expression>
    </fieldIdentifier>
    <fieldMember>
        <expression>profileType</expression>
    </fieldMember>
    <fieldGetter>
        <expression>getProfileType</expression>
    </fieldGetter>
    <fieldSetter>
        <expression>setProfileType</expression>
    </fieldSetter>
</field>

JOOQ version: 3.14.16, Java 8

CodePudding user response:

Why do things behave this way?

The reason is that you have a name mismatch:

Query

ts.PROFILE_TYPE_ID,

DTO

private ProfileType profileType;

If you want to rely on the reflection based DefaultRecordMapper, then you must name those things accordingly, otherwise, they won't be mapped. The fact that you have a converter is irrelevant, if the names don't match. Imagine you had 20 columns of type ProfileType. You wouldn't want to have DefaultRecordMapper map values purely based on their type.

Regarding your comments:

I have added some logs just to check if the converter is triggered at all and I see that the converter is triggered as expected (from method of the Converter class is called during the execution of the JOOQ SQL query)

Yes of course. The Converter belongs to the projected column. The conversion happens before the mapping (i.e. the into(TestSuiteDto.class) call)

Solutions

There are multiple alternatives to solve this:

  • Call your DTO attribute profileTypeId, or to add JPA annotations to it to map between SQL names and Java names
  • Rename your SQL column (in DDL)
  • Alias your SQL column using PROFILE_TYPE_ID.as("profile_type")
  • Use a computed column PROFILE_TYPE and attach the converter to that, keeping the PROFILE_TYPE_ID as it is (you can also use client side computed columns for that, in order not to affect your schema)
  • Use type safe constructor based mapping, rather than reflection based mapping, e.g. using fetch(Records.mapping(TestSuiteDto::new))

There are probably more possible solutions.

  • Related