【问题标题】:Spring Data for Apache Cassandra converts java.time.LocalDateTime to UTCApache Cassandra 的 Spring Data 将 java.time.LocalDateTime 转换为 UTC
【发布时间】:2017-11-10 22:43:13
【问题描述】:

我试图在我的 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 

【问题讨论】:

  • 设置时间格式的时候为什么不用Locale

标签: java spring cassandra timezone spring-data-cassandra


【解决方案1】:

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

LocalDate/LocalDateTime 表示相对于您的本地时间的时间点。在保存日期/时间之前,需要使用时区进行增强以计算通用表示,可以保存。

Spring Data 使用您的系统默认时区 (Date.from(source.atZone(systemDefault()).toInstant()))。

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

【讨论】:

  • 谢谢!那成功了。我的印象是 LocalDate 和 LocalDateTime 中的“本地”意味着这些值与时区无关,但听起来恰恰相反。
  • 我的困惑之源——来自 LocalDateTime 的 javadoc “ISO-8601 日历系统中没有时区的日期时间,例如 2007-12-03T10:15:30。”
  • 小修正:Cassandra 以毫秒为单位存储时间戳,而不是秒。
  • 似乎当 spring 数据持久化时,localdatetime 将始终根据您的系统时区转换为 UTC。谁知道如何欺骗你的时区是“UTC”的spring数据?
【解决方案2】:

我确实想在我的项目中使用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 让我了解在哪里寻找解决方案!

【讨论】:

    猜你喜欢
    • 2021-08-22
    • 2015-04-20
    • 2017-07-13
    • 1970-01-01
    • 2021-10-16
    • 1970-01-01
    • 1970-01-01
    • 2020-12-02
    • 2019-12-23
    相关资源
    最近更新 更多