Skip to content

Enhancement proposal: give jdbcclient access to conversion services for converting custom object types from db. #33467

Open
@alexanderankin

Description

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    in: dataIssues in data modules (jdbc, orm, oxm, tx)status: waiting-for-triageAn issue we've not yet triaged or decided on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions