如何解决Spring R2DBC DatabaseClient 丢失 TZ 信息
我有一个带有一些 Instant 类型字段的数据库实体。
org.springframework.data.r2dbc.core.DatabaseClient(现在已弃用),有一个方法 .as(..)
可以自动映射到一个 java 实体,它也遵守时区。不确定内部是如何发生的。
但是使用没有自动映射器的 org.springframework.r2dbc.core.DatabaseClient,我必须使用一个 .map(...)
来提供行,我像这样映射它们
row.get("blah",Instant.class)
但它只是在我当地的 TZ 中给出时间,而不是 UTC。
有人知道根本原因吗?谢谢。
解决方法
简答
我假设您使用的是 PostgresSQL 数据库和适当的驱动程序。此外,我假设您使用 TIMESTAMP
数据库类型作为 Instant
归档。要从 org.springframework.r2dbc.core.DatabaseClient
获取正确的时区,您可以使用 TIMESTAMP WITH TIME ZONE
作为数据类型。
长答案
我创建了一个测试存储库来测试 R2dbcRepository
和 R2DBC DatabaseClient
将 timestamp
和 timestampz
检索到 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",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_with_tz)
.containsExactly(1,now);
}
}
结果表明这两种方法并不一致:
-
R2dbcRepository
测试始终在所有 TZ 中通过 -
DatabaseClient
测试仅在测试在 UTC TZ 中运行时通过,否则失败。原因是 DB 中定义为TIMESTAMP
的字段会获得 TZ 偏移量。 -
TIMESTAMPZ
字段始终在两种情况下都能正常工作
要复制的回购:https://github.com/xp-vit/r2dbc-timestamps
创建并发布给 Spring 团队至少讨论:https://github.com/spring-projects/spring-data-r2dbc/issues/608
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。