如何在mongodb中保存一个java.time.Instant并无一例外地加载相同的值?
How to save a java.time.Instant in mongodb and load the same value out without exception?
我希望 java.time.Instant
具有相同的值进出 mongo:
data class Person(@Id val name: String, val born: Instant)
@ExtendWith(SpringExtension::class)
@SpringBootTest
class MongoMappingTest(@Autowired private val mongoTemplate: MongoTemplate)
{
@Test
fun `Test custom mapping of the Instant data type`()
{
testFor(Instant.MAX) // This throws java.lang.ArithmeticException
// Failed to convert from type [java.time.Instant] to type [java.util.Date]
testFor(Instant.now()) // This fails with:
// Expected :Person(name=Jim, born=2021-04-07T16:56:10.838Z)
// Actual :Person(name=Jim, born=2021-04-07T16:56:10.838228297Z)
}
fun testFor(instant: Instant)
{
val jim = Person("Jim", instant)
mongoTemplate.save(jim)
val jimOut = mongoTemplate.findById("Jim", Person::class.java)
Assertions.assertThat(jim).isEqualTo(jimOut)
}
}
Instant
首先转换为 java.util.Date
(不知道为什么会这样),然后转换为 BSON Date
数据类型。后者只有毫秒精度,所以我们需要放弃它。我做了自己的表示,我正在尝试替换它(在 docs 之后):
data class InstantRepresentation
(
val seconds: Long, val nanoseconds: Int
)
class InstantWriteConverter: Converter<Instant, InstantRepresentation>
{
override fun convert(source: Instant): InstantRepresentation
{
return InstantRepresentation(source.epochSecond, source.nano)
}
}
class InstantReadConverter: Converter<InstantRepresentation, Instant>
{
override fun convert(source: InstantRepresentation): Instant?
{
return Instant.ofEpochSecond (source.seconds, source.nanoseconds.toLong())
}
}
@Configuration
class MongoConfig: AbstractMongoClientConfiguration()
{
public override fun getDatabaseName(): String
{
return "db-test"
}
public override fun configureConverters(adapter: MongoConverterConfigurationAdapter)
{
adapter.registerConverter(InstantWriteConverter())
adapter.registerConverter(InstantReadConverter())
}
}
然而,一切都没有改变。我的转换代码没有被提取。 spring 似乎仍在尝试将我的 Instant
转换为 java.util.Date
.
我试图用 @Field(targetType = FieldType.IMPLICIT)
注释 Person.born
来告诉系统我想将它存储为一个对象,但没有帮助。
试图拯救第 10 亿年出生的人是没有意义的。不要那样做。让您的构造函数验证出生日期并拒绝未来的出生日期和超过 20 000 000 年前的出生日期。 2000万年前没有人类诞生
对于 precision:转换为 java.util.Date
可能是过去的遗留问题。如果数据库本身的精度高于毫秒,这应该被认为是一个错误。你能得到更新版本的 JDBC 驱动程序吗?与此同时,一个解决方法是在保存之前截断 Java 中的值:
born = born.truncatedTo(ChronoUnit.MILLIS);
这是解决方案:
val secondsFieldName = "seconds"
val nanoSecondsFieldname = "nanoseconds"
@WritingConverter
class InstantWriteConverter: Converter<Instant, Document>
{
override fun convert(source: Instant): Document
{
return Document(mapOf(
secondsFieldName to source.epochSecond,
nanoSecondsFieldname to source.nano.toLong()))
}
}
@ReadingConverter
class InstantReadConverter: Converter<Document, Instant>
{
override fun convert(source: Document): Instant
{
return Instant.ofEpochSecond (
source[secondsFieldName] as Long,
source[nanoSecondsFieldname] as Long)
}
}
@Configuration
class MongoConfig
{
@Bean
fun customConversions() = MongoCustomConversions(listOf(
InstantWriteConverter(),
InstantReadConverter()))
}
要点:
您不能将您的类型转换为您喜欢的任何类型。 org.bson.Document
有效。其他一些 类 也可以工作,但不清楚应该使用哪些或哪些。
我希望 java.time.Instant
具有相同的值进出 mongo:
data class Person(@Id val name: String, val born: Instant)
@ExtendWith(SpringExtension::class)
@SpringBootTest
class MongoMappingTest(@Autowired private val mongoTemplate: MongoTemplate)
{
@Test
fun `Test custom mapping of the Instant data type`()
{
testFor(Instant.MAX) // This throws java.lang.ArithmeticException
// Failed to convert from type [java.time.Instant] to type [java.util.Date]
testFor(Instant.now()) // This fails with:
// Expected :Person(name=Jim, born=2021-04-07T16:56:10.838Z)
// Actual :Person(name=Jim, born=2021-04-07T16:56:10.838228297Z)
}
fun testFor(instant: Instant)
{
val jim = Person("Jim", instant)
mongoTemplate.save(jim)
val jimOut = mongoTemplate.findById("Jim", Person::class.java)
Assertions.assertThat(jim).isEqualTo(jimOut)
}
}
Instant
首先转换为 java.util.Date
(不知道为什么会这样),然后转换为 BSON Date
数据类型。后者只有毫秒精度,所以我们需要放弃它。我做了自己的表示,我正在尝试替换它(在 docs 之后):
data class InstantRepresentation
(
val seconds: Long, val nanoseconds: Int
)
class InstantWriteConverter: Converter<Instant, InstantRepresentation>
{
override fun convert(source: Instant): InstantRepresentation
{
return InstantRepresentation(source.epochSecond, source.nano)
}
}
class InstantReadConverter: Converter<InstantRepresentation, Instant>
{
override fun convert(source: InstantRepresentation): Instant?
{
return Instant.ofEpochSecond (source.seconds, source.nanoseconds.toLong())
}
}
@Configuration
class MongoConfig: AbstractMongoClientConfiguration()
{
public override fun getDatabaseName(): String
{
return "db-test"
}
public override fun configureConverters(adapter: MongoConverterConfigurationAdapter)
{
adapter.registerConverter(InstantWriteConverter())
adapter.registerConverter(InstantReadConverter())
}
}
然而,一切都没有改变。我的转换代码没有被提取。 spring 似乎仍在尝试将我的 Instant
转换为 java.util.Date
.
我试图用 @Field(targetType = FieldType.IMPLICIT)
注释 Person.born
来告诉系统我想将它存储为一个对象,但没有帮助。
试图拯救第 10 亿年出生的人是没有意义的。不要那样做。让您的构造函数验证出生日期并拒绝未来的出生日期和超过 20 000 000 年前的出生日期。 2000万年前没有人类诞生
对于 precision:转换为 java.util.Date
可能是过去的遗留问题。如果数据库本身的精度高于毫秒,这应该被认为是一个错误。你能得到更新版本的 JDBC 驱动程序吗?与此同时,一个解决方法是在保存之前截断 Java 中的值:
born = born.truncatedTo(ChronoUnit.MILLIS);
这是解决方案:
val secondsFieldName = "seconds"
val nanoSecondsFieldname = "nanoseconds"
@WritingConverter
class InstantWriteConverter: Converter<Instant, Document>
{
override fun convert(source: Instant): Document
{
return Document(mapOf(
secondsFieldName to source.epochSecond,
nanoSecondsFieldname to source.nano.toLong()))
}
}
@ReadingConverter
class InstantReadConverter: Converter<Document, Instant>
{
override fun convert(source: Document): Instant
{
return Instant.ofEpochSecond (
source[secondsFieldName] as Long,
source[nanoSecondsFieldname] as Long)
}
}
@Configuration
class MongoConfig
{
@Bean
fun customConversions() = MongoCustomConversions(listOf(
InstantWriteConverter(),
InstantReadConverter()))
}
要点:
您不能将您的类型转换为您喜欢的任何类型。 org.bson.Document
有效。其他一些 类 也可以工作,但不清楚应该使用哪些或哪些。