2

I am using spring-data-jdbc to persist the following entity into PostgreSQL

@Table("abc_configurations")
data class AbcConfiguration(
    val name: String,
    val distribution: Map<String, Int>
)

The distribution is a jsonb column in PostgreSQL table:

CREATE TABLE abc_configurations
(
    name          VARCHAR(50)    PRIMARY KEY,
    distribution  JSONB    NOT NULL
);

I have created the following custom converter for this:

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import org.postgresql.util.PGobject
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.convert.converter.Converter
import org.springframework.data.convert.ReadingConverter
import org.springframework.data.convert.WritingConverter
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions
import org.springframework.data.jdbc.core.convert.JdbcValue
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories
import java.sql.JDBCType

@Configuration
@EnableJdbcRepositories
class DataConfig : AbstractJdbcConfiguration() {

    @Autowired
    private lateinit var objectMapper: ObjectMapper;

    @Bean
    override fun jdbcCustomConversions(): JdbcCustomConversions {
        val converters = listOf(
            DistributionWritingConverter(objectMapper),
            DistributionReadingConverter(objectMapper)
        )
        return JdbcCustomConversions(converters)
    }

    @WritingConverter
    class DistributionWritingConverter(private val objectMapper: ObjectMapper) : Converter<Map<String, Int>, JdbcValue> {
        override fun convert(source: Map<String, Int>): JdbcValue {
            return JdbcValue.of(objectMapper.writeValueAsString(source), JDBCType.VARCHAR)
        }
    }

    @ReadingConverter
    class DistributionReadingConverter(private val objectMapper: ObjectMapper) : Converter<PGobject, Map<String, Int>> {
        override fun convert(source: PGobject): Map<String, Int> {
            return objectMapper.readValue(source.value, object : TypeReference<Map<String, Int>>() {})
        }
    }
}

When I persist an Entity, it works fine, and the distribution Map gets persisted into JSONB column as expected.

The issue is when I read the entity, I am getting following Error: Couldn't find PersistentEntity for type class java.lang.Integer!

org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.lang.Integer!
    at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:79) ~[spring-data-commons-2.4.5.jar:2.4.5]
    at org.springframework.data.jdbc.core.convert.SqlGeneratorSource.lambda$getSqlGenerator$0(SqlGeneratorSource.java:62) ~[spring-data-jdbc-2.1.5.jar:2.1.5]
    at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
    at org.springframework.data.jdbc.core.convert.SqlGeneratorSource.getSqlGenerator(SqlGeneratorSource.java:61) ~[spring-data-jdbc-2.1.5.jar:2.1.5]
    at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.sql(DefaultDataAccessStrategy.java:583) ~[spring-data-jdbc-2.1.5.jar:2.1.5]
    at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.findAllByPath(DefaultDataAccessStrategy.java:362) ~[spring-data-jdbc-2.1.5.jar:2.1.5]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.4.jar:5.3.4]

I have tried changing the @ReadingConverter method to following which did not work:

  • Converter<String, Map<String, Int>>
  • Converter<JdbcValue, Map<String, Int>>

The ReadingConverter is not being used.

How can I tell spring to use the custom @ReadingConverter

Update (01.09.2021):

As mentioned by Jen and Susan, this is an issue with the spring-data-jdbc library. I moved ahead with the following work around.

data class Distribution(val value: Map<String, Int>)


@Table("abc_configurations")
data class AbcConfiguration(
    val name: String,
    val distribution: Distribution
)
Anil Bharadia
  • 2,760
  • 6
  • 34
  • 46

1 Answers1

2

Maps do get special handling in Spring Data JDBC so you can't simple use converters for them. Instead wrap the map in a custom type and register converters for that type.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • Thanks Jens, that helped fix the issue. Do you have any plan to fix this issue, so that we don't have to apply this workaround. Something like an annotation on the column that overrides this special handling? – Anil Bharadia Aug 31 '21 at 09:06
  • We are thinking about how to improve JSON support. I'm not at all sure if that will/should allow for direct conversion of Maps though – Jens Schauder Aug 31 '21 at 10:07