Search code examples
javaspringcassandradatastax-java-driverspring-data-cassandra

Custom codecs read issue in Spring data cassandra


I have a timestamp field in my Cassandra table which I want to map to a Java Instant type. Its fairly easy to do achieve this while writing.

I add the custom codecs.

@Override
protected ClusterBuilderConfigurer getClusterBuilderConfigurer() {
    return clusterBuilder -> {

        clusterBuilder.getConfiguration().getCodecRegistry()
                      .register(InstantCodec.instance,
                                LocalDateCodec.instance,
                                LocalTimeCodec.instance);
        return clusterBuilder;
    };
}

Tell spring to not convert my Instant to some other type.

private enum InstantWriteConverter implements Converter<Instant, Instant> {
    INSTANT;

    @Override
    public Instant convert(Instant source) {
        return source;
    }
}

This way Instant is passed along as it is and gets handled by the InstantCodec.

But when reading back from Cassandra, the read timestamp gets mapped to a Date and I am not able to change this behaviour. Due to this, I need to add a special constructer to my entity just to convert the Date to Instant.

My analysis. When parsing the Row data, Cassandra performs a look up to find an appropriate Codec. It does not respect the parameter types of the provided entity constructor and simply picks up the first codec which can handle the row data. In my case it picks up the timestamp->date codec because its present before the timestamp->instant codec in the CodecRegistry codecs list.

Any way to directly convert the timestamp to Instant ?

EDIT

tried registering read write converters, but the read converter is not getting used.

    @WritingConverter
private enum InstantWriteConverter implements Converter<Instant, Long> {
    INSTANT;

    @Override
    public Long convert(Instant source) {
        return source.toEpochMilli();
    }
}

@ReadingConverter
private enum InstantReadConverter implements Converter<Long, Instant> {
    INSTANT;

    @Override
    public Instant convert(Long source) {
        return Instant.ofEpochMilli(source);
    }
}

Solution

  • Got it working. The read converter needs to be on Row->Class level.

        @Override
        protected ClusterBuilderConfigurer getClusterBuilderConfigurer() {
            return clusterBuilder -> {
    
                clusterBuilder.getConfiguration().getCodecRegistry()
                              .register(InstantCodec.instance,
                                        LocalDateCodec.instance,
                                        LocalTimeCodec.instance);
                return clusterBuilder;
            };
        }
    
        @Override
        public CustomConversions customConversions() {
            return new CustomConversions(
                    Arrays.asList(ReadConverter.INSTANCE,
                                  InstantWriteConverter.INSTANCE,
                                  LocalTimeWriteConverter.INSTANCE,
                                  DurationWriteConverter.INSTANCE,
                                  LocalDateWriteConverter.INSTANCE));
        }
    
    @ReadingConverter
    private enum ReadConverter implements Converter<Row, FlightFareInfo> {
        INSTANCE;
    
        @Override
        public FlightFareInfo convert(Row source) {
    
            return FlightFareInfo.convertFromRow(source);
        }
    }