Spring Apache Cassandra 的数据将 java.time.LocalDateTime 转换为 UTC

Spring Data for Apache Cassandra converts java.time.LocalDateTime to UTC

我试图在我的 Cassandra 数据库中保留一个 java.time.LocalDateTime 对象并使其与时区无关。我正在使用 Spring Data Cassandra 来执行此操作。

问题是在某个地方,有些东西正在处理这些 LocalDateTime 对象,就好像它们在我的服务器的时区中一样,并在将它们存储在数据库中时将它们偏移到 UTC 时间。

这是错误还是功能?我可以通过某种方式解决它吗?

配置:

@Configuration
@EnableCassandraRepositories(
    basePackages = "my.base.package")
public class CassandraConfig extends AbstractCassandraConfiguration{

    @Override
    protected String getKeyspaceName() {
        return "keyspacename";
    }

    @Bean
    public CassandraClusterFactoryBean cluster() {
        CassandraClusterFactoryBean cluster =
            new CassandraClusterFactoryBean();
        cluster.setContactPoints("127.0.0.1");
        cluster.setPort(9142);
        return cluster;
    }

    @Bean
    public CassandraMappingContext cassandraMapping()
        throws ClassNotFoundException {
        return new BasicCassandraMappingContext();
    }
}

我希望保留的预订记录:

@Table("booking")
public class BookingRecord {
    @PrimaryKeyColumn(
        ordinal = 0,
        type = PrimaryKeyType.PARTITIONED
    )
    private UUID bookingId = null;

    @PrimaryKeyColumn(
        ordinal = 1,
        type = PrimaryKeyType.CLUSTERED,
        ordering = Ordering.ASCENDING
    )
    private LocalDateTime startTime = null;
    ...
}

简单的存储库界面:

@Repository
public interface BookingRepository extends CassandraRepository<BookingRecord> { }

保存调用:

...

@Autowired
BookingRepository bookingRepository;

...

public void saveBookingRecord(BookingRecord bookingRecord) {
    bookingRepository.save(bookingRecord);
}

这是用于在 BookingRecord 中填充开始时间的字符串:

"startTime": "2017-06-10T10:00:00Z"

这是保留时间戳后 cqlsh 的输出:

cqlsh:keyspacename> select * from booking ;

 bookingid                            | starttime               
--------------------------------------+--------------------------------
 8b640c30-4c94-11e7-898b-6dab708ec5b4 | 2017-06-10 15:00:00.000000+0000 

Cassandra 将日期 (timestamp) 存储为 milliseconds since epoch without a specific timezone information。时区数据在 Cassandra 之上的层中处理。

LocalDate/LocalDateTime 表示相对于您当地时间的时间点。在date/time可以保存之前,需要用时区增强来计算泛型表示,可以保存。

Spring 数据使用系统默认时区 (Date.from(source.atZone(systemDefault()).toInstant()))。

如果您需要时区精度并希望省略任何隐式时区转换,请直接使用 java.util.Date,它对应于 Cassandra 的(准确地说是 Datastax 驱动程序)存储格式表示。

我确实想在我的项目中使用 LocalDateTimeLocalDate,而不是 java.util.Date,因为它们更新且功能更吸引人。

经过大量搜索,我找到了解决方法。

首先,您必须创建 Spring 的 Converter 接口的自定义实现,如下所示:

一个用于 DateLocalDateTime:

public class DateToLocalDateTime implements Converter<Date, LocalDateTime> {

    @Override
    public LocalDateTime convert(Date source) {
        return source == null ? null : LocalDateTime.ofInstant(source.toInstant(), ZoneOffset.UTC);
    }

}

还有一个用于 LocalDateTimeDate:

public class LocalDateTimeToDate implements Converter<LocalDateTime, Date> {

    @Override
    public Date convert(LocalDateTime source) {
        return source == null ? null : Date.from(source.toInstant(ZoneOffset.UTC));
    }

}

最后,您必须重写CassandraConfig中的customConversions方法,如下所示:

@Configuration
@EnableCassandraRepositories(basePackages = "my.base.package")
public class CassandraConfig extends AbstractCassandraConfiguration{

    @Override
    protected String getKeyspaceName() {
        return "keyspacename";
    }

    @Override
    public CustomConversions customConversions() {
        List<Converter> converters = new ArrayList<>();

        converters.add(new DateToLocalDateTime());
        converters.add(new LocalDateTimeToDate());

        return new CustomConversions(converters);
    }

    @Bean
    public CassandraClusterFactoryBean cluster() {
        CassandraClusterFactoryBean cluster =
            new CassandraClusterFactoryBean();
        cluster.setContactPoints("127.0.0.1");
        cluster.setPort(9142);
        return cluster;
    }

    @Bean
    public CassandraMappingContext cassandraMapping()
        throws ClassNotFoundException {
        return new BasicCassandraMappingContext();
    }
}

感谢 mp911de 让我了解了在哪里寻找解决方案!