【问题标题】:Spring R2DBC DatabaseClient losing TZ informationSpring R2DBC DatabaseClient 丢失 TZ 信息
【发布时间】:2021-02-05 10:37:30
【问题描述】:

我有一个带有一些 Instant 类型字段的 db 实体。 org.springframework.data.r2dbc.core.DatabaseClient (现在已弃用),有一个方法.as(..) 可以自动映射到一个java实体,它也尊重时区。不知道内部是怎么发生的。

但是使用没有自动映射器的 org.springframework.r2dbc.core.DatabaseClient,我必须使用提供行的.map(...),然后像这样映射它们

row.get("blah", Instant.class) 但它只是在我当地的 TZ 中给出时间,而不是 UTC。

有人知道根本原因吗?谢谢。

【问题讨论】:

    标签: java spring spring-data-r2dbc r2dbc


    【解决方案1】:

    简答

    我假设您使用的是 PostgresSQL 数据库和适当的驱动程序。另外,我假设您正在为您的Instant 归档使用TIMESTAMP DB 类型。要从 org.springframework.r2dbc.core.DatabaseClient 获取正确的时区,您可以使用 TIMESTAMP WITH TIME ZONE 作为数据类型。

    长答案

    我创建了一个测试存储库来测试 R2dbcRepository 和 R2DBC DatabaseClient 的行为,以将 timestamptimestampz 检索到 Instant。为此,我创建了下表: CREATE TABLE test_table (id SERIAL PRIMARY KEY, timestamp_without_tz TIMESTAMP, timestamp_with_tz TIMESTAMP WITH TIME ZONE);

    并实现了以下服务:

    package com.patotski.r2dbctimestamps.service;
    
    
    import com.patotski.r2dbctimestamps.domain.TestEntity;
    import com.patotski.r2dbctimestamps.repo.TestEntityRepo;
    import lombok.RequiredArgsConstructor;
    import org.springframework.r2dbc.core.DatabaseClient;
    import org.springframework.stereotype.Service;
    import reactor.core.publisher.Mono;
    
    import java.time.Instant;
    
    @Service
    @RequiredArgsConstructor
    public class TestEntityService {
    
        private final TestEntityRepo testEntityRepo; // uses standard R2dbcRepository implementation
        private final DatabaseClient databaseClient;
    
        public Mono<TestEntity> saveUsingRepo(TestEntity entity) {
            return testEntityRepo.save(entity);
        }
    
        public Mono<TestEntity> getByIdFromRepo(int id) {
            return testEntityRepo.findById(id);
        }
    
        public Mono<TestEntity> saveUsingDbClient(TestEntity entity) {
            return databaseClient.sql("INSERT INTO test_table (timestamp_without_tz, timestamp_with_tz) VALUES(:timestamp_without_tz, :timestamp_with_tz)")
                    .bind("timestamp_without_tz", entity.getTimestamp_without_tz())
                    .bind("timestamp_with_tz", entity.getTimestamp_with_tz())
                    .map(row -> TestEntity.builder()
                            .id(row.get("id", Integer.class))
                            .timestamp_with_tz(row.get("timestamp_with_tz", Instant.class))
                            .timestamp_without_tz(row.get("timestamp_without_tz", Instant.class))
                            .build()).first();
        }
    
        public Mono<TestEntity> getByIdFromDbClient(int id) {
            return databaseClient.sql("SELECT * from test_table where id = :id")
                    .bind("id", id)
                    .map(row -> TestEntity.builder()
                            .id(row.get("id", Integer.class))
                            .timestamp_with_tz(row.get("timestamp_with_tz", Instant.class))
                            .timestamp_without_tz(row.get("timestamp_without_tz", Instant.class))
                            .build()).first();
        }
    
    }
    
    

    并创建了仅存储实体并检索断言时间戳的测试:

    @SpringBootTest
    class TestEntityServiceTest {
    
        @Autowired
        TestEntityService testEntityService;
    
        @Test
        @DisplayName("Should store and retrieve Entity with both timestamp fields in correct timezones using R2DBC repo.")
        void shouldStoreCorrectTimestampsAndRetriveWithRepo() {
            Instant now = Instant.now();
            TestEntity entity = TestEntity.builder()
                    .timestamp_with_tz(now)
                    .timestamp_without_tz(now)
                    .build();
    
            TestEntity saved = testEntityService.saveUsingRepo(entity).block();
    
            Assertions.assertThat(testEntityService.getByIdFromRepo(saved.getId()).block()).isNotNull()
                    .extracting(TestEntity::getId,
                            TestEntity::getTimestamp_without_tz,
                            TestEntity::getTimestamp_with_tz)
                    .containsExactly(saved.getId(), now, now);
        }
    
        @Test
        @DisplayName("Should store and retrieve Entity with both timestamp fields in correct timezones using R2DBC DatabaseClient.")
        void shouldStoreCorrectTimestampsAndRetriveWithDbClient() {
            Instant now = Instant.now();
            TestEntity entity = TestEntity.builder()
                    .timestamp_with_tz(now)
                    .timestamp_without_tz(now)
                    .build();
    
            testEntityService.saveUsingDbClient(entity).block();
    
            Assertions.assertThat(testEntityService.getByIdFromDbClient(1).block()).isNotNull()
                    .extracting(TestEntity::getId,
                            TestEntity::getTimestamp_without_tz,
                            TestEntity::getTimestamp_with_tz)
                    .containsExactly(1, now, now);
        }
    }
    

    结果表明这两种方法并不一致:

    • R2dbcRepository 测试总是在所有 TZ 中通过
    • DatabaseClient 测试仅在测试在 UTC TZ 中运行时通过,否则失败。原因是DB中定义为TIMESTAMP的字段获取了TZ偏移。
    • TIMESTAMPZ 字段在这两种情况下都能正常工作

    repo 重现:https://github.com/xp-vit/r2dbc-timestamps

    为 Spring 团队创建和发布至少讨论:https://github.com/spring-projects/spring-data-r2dbc/issues/608

    【讨论】:

      猜你喜欢
      • 2020-01-02
      • 2019-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-27
      • 2018-02-09
      相关资源
      最近更新 更多