Spring Data for Apache Cassandra将java.time.LocalDateTime转换为UTC。

4
我试图在我的Cassandra数据库中永久保存一个java.time.LocalDateTime对象,并保持它的时区无关性。我使用Spring Data Cassandra来实现这一点。
问题是,在某个地方,有些东西正在处理这些LocalDateTime对象,好像它们处于我的服务器的时区,并在将它们存储到数据库中时将它们偏移到UTC时间。
这是一个bug还是一个功能?我能以某种方式解决它吗?
配置:
@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的字符串:
"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 

为什么在设置时间格式时不使用“Locale”? - bananas
2个回答

9
Cassandra将日期(timestamp)存储为自纪元起的毫秒数,没有特定的时区信息。时区数据在Cassandra上方的层中处理。 LocalDate/LocalDateTime表示相对于本地时间的时间点。在保存日期/时间之前,需要增加时区以计算通用表示形式,然后才能保存。
Spring Data使用系统默认时区(Date.from(source.atZone(systemDefault()).toInstant()))。
如果您需要时区精度并想省略任何隐式时区转换,请直接使用java.util.Date,它与Cassandra(准确说是Datastax Driver)的存储格式表示相对应。

谢谢!那解决了问题。我原本以为LocalDate和LocalDateTime中的“Local”表示值与时区无关,但听起来恰恰相反。 - jcslater
我困惑的源头- 来自于LocalDateTime的Javadoc"一个ISO-8601日历系统中没有时区的日期时间,例如2007-12-03T10:15:30。" - jcslater
小修正:Cassandra 存储的时间戳是毫秒级别,而不是秒。 - Tobias Hermann
当使用Spring Data进行持久化时,似乎LocalDateTime将始终根据您的系统时区转换为UTC。谁知道如何欺骗Spring Data以使您的时区为“UTC”? - mike

6

我实际上希望在我的项目中使用LocalDateTimeLocalDate,而不是java.util.Date,因为它们更加新颖并具有更多有吸引力的功能。

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

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

一个将Date转换为LocalDateTime的转换器:

public class DateToLocalDateTime implements Converter<Date, LocalDateTime> {

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

}

还有一个将LocalDateTime转换为Date的方法:

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给了我一个方向,让我知道从哪里寻找解决方案!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接