5

I use JsonNode in my Client class to deal with a field with JSON type in MySQL 8 database. It works very well even with API requests. But when I enable caching with Redis (which I really need it), I notice that Redis is not able to serialize JsonNode. I searched the internet for changing the serialization method of Redis. and came up with the following code:

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        return Jackson2ObjectMapperBuilder.json()
                .serializationInclusion(JsonInclude.Include.NON_NULL) // Don’t include null values
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) //ISODate
                .build();
    }

    @Bean
    public RedisTemplate getRedisTemplate(ObjectMapper objectMapper, RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }

    @Bean
    @Primary
    public RedisCacheConfiguration defaultCacheConfig(ObjectMapper objectMapper) {
        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
    }

}

It serializes my objects successfully and put them in Redis successfully, but cannot to deserialize them!. And produces the following error:

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.Client (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.example.Client is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @4683d900)]

And this is my client class:

@Data
@Entity
@Table( name = "clients" )
@EntityListeners(AuditingEntityListener.class)
@TypeDef(name = "json", typeClass = JsonStringType.class)
public class Client  {
    /**
     * Id of the user
     */
    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long id;
    /**
     * UUID of the user.
     */
    @Column( name = "uuid", unique = true, updatable = false, nullable = false )
    private String uuid;


    /**
     * Name of the client
     */
    @Column( name = "name", unique = true, nullable = false )
    @Size( min=4, max=128, message = "client.exception.name.size" )
    @NotBlank( message = "client.exception.name.isBlank" )
    private String name;


    /**
     * Status of the client. Each client could have several statues.
     * 1.
     */
    @Column( name = "client_status_id" )
    @NotNull( message = "client.exception.status.isNull" )
    @Enumerated( EnumType.ORDINAL )
    private ClientStatus clientStatus = ClientStatus.Undefined;


    /**
     * Email address of the client.
     */
    @Column( name = "email" )
    @NotBlank( message = "client.exception.email.isBlank")
    @Size( min=5, max = 128, message = "client.exception.email.size")
    @Email( message = "client.exception.email.notValid" )
    private String email;

    /**
     * ClientNotFoundByIdException's phone number.
     */
    @Column( name = "phone" )
    @NotBlank( message = "client.exception.phone.isBlank" )
    @Size( min=3, max = 32, message = "client.exception.phone.size")
    @Phone( message = "client.exception.phone.notValid" )
    private String phone;

    /**
     * Whether client is active or not.
     */
    @Column( name = "is_active" )
    private Boolean isActive = true;

    /**
     * Default timezone of the client.
     */
    @Column( name = "timezone" )
    @NotBlank( message = "client.exception.timezone.isBlank" )
    @Size( min = 4, max = 64, message = "client.exception.timezone.size" )
    @TimeZone( message = "client.exception.timezone.notValid" )
    private String timezone;


    /**
     * Country code of the client in ISO 3166-2
     */
    @Column( name = "country" )
    @NotBlank( message = "client.exception.country.isBlank" )
    @Size( min = 2, max = 4, message = "client.exception.country.size" )
    @Country( message = "client.exception.country.notValid" )
    private String country;


    @Column( name = "language" )
    @NotBlank( message = "client.exception.language.isBlank" )
    @Size( min = 2, max = 3, message = "client.exception.language.size" )
    @Language
    private String language;


    /**
     * Extra fields for client in json
     */
    @Column( name = "fields", columnDefinition = "json" )
    @Type( type = "json" )
    @NotNull( message = "client.exception.fields.isNull" )
    private JsonNode fields;


    /**
     * Creation time of the record.
     */
    @Column( name = "created_at", updatable = false )
    @NotNull( message = "client.exception.createdAt.isNull")
    @CreatedDate
    private Instant createdAt;


    /**
     * The user that created the record.
     */
    @CreatedBy
    @ManyToOne
    @JoinColumn( name = "created_by" )
    private User createdBy;


    /**
     * In which time the record is modified.
     */
    @Column( name = "updated_at" )
    @NotNull( message = "client.exception.updatedAt.isNull" )
    @LastModifiedDate
    private Instant updatedAt;


    /**
     * By whom the record is modified.
     */
    @LastModifiedBy
    @ManyToOne
    @JoinColumn( name = "updated_by" )
    private User updatedBy;


    /**
     * The time in which the record is deleted.
     */
    private Instant deletedAt;


    /**
     * By whom the record is deleted.
     */
    @ManyToOne
    @JoinColumn( name = "deleted_by" )
    private User deleteBy;


    /**
     * Version of the record.
     */
    @Version
    private Long version;
}

Update:

I noticed tht the error is not related to JsonNode, It throws the exception when I use GenericJackson2JsonRedisSerializer.

7
  • Did you consider using RedisJSON.io? Commented Sep 8, 2019 at 16:16
  • @GuyKorland well I don't think that it could solve my problem. Commented Sep 8, 2019 at 16:28
  • @user335870 did you try making your client class implements java.io.Serializable? Commented Sep 9, 2019 at 12:19
  • @FarrukhAhmed Yes, I tried. same problem again. Even I tried to write converters, but it didn't work either Commented Sep 9, 2019 at 17:29
  • how are you actually reading/writing this data? Commented Sep 9, 2019 at 18:18

2 Answers 2

3

I had very similar problem and the solution was strange, but simple - remove devtools dependency.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>
Sign up to request clarification or add additional context in comments.

Comments

0

Solved it by defining a custom ReadingConverter and WritingConverter. With those two, I was able to successfully deserialize properties of type JsonNode.

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
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.redis.core.convert.RedisCustomConversions
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import org.springframework.stereotype.Component

@WritingConverter
@Component
class JsonNodeToByteArrayConverter() : Converter<JsonNode, ByteArray> {

        private var serializer: Jackson2JsonRedisSerializer<JsonNode> = Jackson2JsonRedisSerializer(JsonNode::class.java)

    init {
        serializer.setObjectMapper(ObjectMapper())
    }

    override fun convert(source: JsonNode): ByteArray {
        return serializer.serialize(source)
    }
}

@ReadingConverter
@Component
class ByteArrayToJsonNodeConverter : Converter<ByteArray, JsonNode> {

        private var serializer: Jackson2JsonRedisSerializer<JsonNode> = Jackson2JsonRedisSerializer(JsonNode::class.java)

    init {
        serializer.setObjectMapper(ObjectMapper())
    }

    override fun convert(source: ByteArray): JsonNode {
        return serializer.deserialize(source)
    }
}

/**
 * Custom mappers are needed to convert JsonNode (otherwise, deserialization fails)
 */
@Configuration
@EnableRedisRepositories
class RedisConfiguration {
    @Bean
    fun redisCustomConversions(
        byteArrayToJsonNodeConverter: ByteArrayToJsonNodeConverter,
        jsonNodeToByteArrayConverter: JsonNodeToByteArrayConverter
    ): RedisCustomConversions {
        return RedisCustomConversions(listOf(byteArrayToJsonNodeConverter, jsonNodeToByteArrayConverter))
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.