Skip to content

Give JdbcClient access to ConversionService for converting custom database object types #33467

Closed
@alexanderankin

Description

@alexanderankin

Enhancement Description: allow JdbcClient to convert custom object types in records. for example, in postgres you may have a jsonb and you'd want to be able to convert that into something so that you can parse it onto your record class while fetching:

record CustomRecord(JsonNode jsonbColumn) {}
jdbcClient.sql("select jsonb_column from some_table").query(CustomRecord.class).list();

code references:

possible solution:

  • refactor DefaultJdbcClient constructors
from their current form
class DefaultJdbcClient {

    private final JdbcOperations classicOps;

    private final NamedParameterJdbcOperations namedParamOps;

    private final Map<Class<?>, RowMapper<?>> rowMapperCache = new ConcurrentHashMap<>();


    public DefaultJdbcClient(DataSource dataSource) {
        this.classicOps = new JdbcTemplate(dataSource);
        this.namedParamOps = new NamedParameterJdbcTemplate(this.classicOps);
    }

    public DefaultJdbcClient(JdbcOperations jdbcTemplate) {
        Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
        this.classicOps = jdbcTemplate;
        this.namedParamOps = new NamedParameterJdbcTemplate(jdbcTemplate);
    }

    public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate) {
        Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
        this.classicOps = jdbcTemplate.getJdbcOperations();
        this.namedParamOps = jdbcTemplate;
    }
}
to this pattern:
class DefaultJdbcClient {
    private final JdbcOperations classicOps;
    private final NamedParameterJdbcOperations namedParamOps;
    private final ConversionService conversionService;
    private final Map<Class<?>, RowMapper<?>> rowMapperCache = new ConcurrentHashMap<>();

    public DefaultJdbcClient(DataSource dataSource) {
        this(new JdbcTemplate(dataSource));
    }

    public DefaultJdbcClient(JdbcOperations jdbcTemplate) {
        this(new NamedParameterJdbcTemplate(jdbcTemplate));
    }

    public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate) {
        this(jdbcTemplate.getJdbcOperations(), jdbcTemplate, DefaultConversionService.getSharedInstance());
    }

    public DefaultJdbcClient(
            JdbcOperations jdbcOperations,
            NamedParameterJdbcOperations namedParameterJdbcOperations,
            ConversionService conversionService
    ) {
        Assert.notNull(jdbcOperations, "jdbcOperations must not be null");
        Assert.notNull(namedParameterJdbcOperations, "namedParameterJdbcOperations must not be null");
        Assert.notNull(conversionService, "conversionService must not be null");
        this.classicOps = jdbcOperations;
        this.namedParamOps = namedParameterJdbcOperations;
        this.conversionService = conversionService;
    }
}
  • add a JdbcClient.create method that is just going to be kept in sync with the bottom constructor (just allow user to pass all components)
  • pass this conversion service where appropriate to simple property row mapper constructors et al
  • change the AutoConfiguration class from
	@Bean
	JdbcClient jdbcClient(NamedParameterJdbcTemplate jdbcTemplate) {
		return JdbcClient.create(jdbcTemplate);
	}

to

	@Bean
	JdbcClient jdbcClient(NamedParameterJdbcTemplate jdbcTemplate, /*kosher?*/ @Nullable ConversionService conversionService) {
		return JdbcClient.create(jdbcTemplate, java.util.Objects.requireNonNullElseGet(conversionService, DefaultConversionService::getSharedInstance));
	}

benefits

you can now register converters as beans and they will be able to be used for jdbcClient queries.

considerations

  • not sure how ok it is to rely on the conversion service bean as I understand its intent was for converting http requests/responses, hence why i made it an optional dependency and defaulted it to: java.util.Objects.requireNonNullElseGet(conversionService, DefaultConversionService::getSharedInstance).
  • alternative to consider is just to write boilerplate, eg. by creating your own row mappers - https://stackoverflow.com/a/78655612

Metadata

Metadata

Assignees

Labels

in: dataIssues in data modules (jdbc, orm, oxm, tx)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions