使用转换器读取与存储LocalTime
java.time里有一个 LocalTime,可用来表示时分秒,如:
#02:10:30 LocalTime localTime1 = LocalTime.parse("02:30:40"); LocalTime localTime2 =LocalTime.of(2, 30, 40)
但我们不能用这个字段来建模,因为存储到数据库时会变成"startingDailyAt" : ISODate("2018-12-26T02:30:40.000+0000")
,自动加上年月日了。如果非要使用 LocalTime来作为我们模型的字段类型,且要将它的值按字符串02:30:40
格式存储到数据库,该如何处理呢?
这里我们讲解基于 SpringData的做法,在SpringData中,我们可以使用贴有@ReadingConverter和@WritingConverter的转换器来进行自定义类型处理,他们分别表示数据库到模型和模型到数据库的数据转换。废话少说,先把模型贴出来:
@Document( collection = "SCHEDULE_OBJECT") public class ScheduleObject extends AuditBaseEntity implements UuidHolder { @Indexed(unique = true) @NotBlank private String name; @NotNull private SystemUnitEnum owner; private LocalTime startingDailyAt; ...... }
模型中的 startingDailyAt 字段就是 LocalTime 类型,下面我们就用它来实现一把上面说的两个转换器。
使用@ReadingConverter读取数据库中的字符串值,并转成模型中的LocalTime
LocalTimeReadConverter.class
import org.bson.Document; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.util.StringUtils; import java.time.LocalTime; @ReadingConverter public class LocalTimeReadConverter implements Converter<String, LocalTime> { @Override public LocalTime convert(String source) { if (source == null) return null; return LocalTime.parse(source); } }
注:
必须满足数据库中的类型为 string,并且实体的类型为 LocalTime时,才会被触发。
如果数据库中的类型为嵌套对象,这里要用Document作为source(未测试)。
使用@WritingConverter将模型中LocalTime字段值存储到数据库中
LocalTimeWriteConverter.class
import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.WritingConverter; import java.time.LocalTime; @WritingConverter public class LocalTimeWriteConverter implements Converter< LocalTime,String> { @Override public String convert(LocalTime source) { if (source == null) return null; return source.toString(); } }
使转换器生效,并测试
FalSpringMongoConfig.class
@Configuration @AutoConfigureBefore(SpringMongoConfig.class) @EnableMongoAuditing(auditorAwareRef = "userAuditor") @EnableMongoRepositories(basePackages = { "com.falsec.pom.policy.repository"}, repositoryBaseClass = FalRepositoryImpl.class) @ConfigurationProperties(prefix = "spring.data.mongodb.data") public class FalSpringMongoConfig extends AbstractMongoConfiguration { private String host, database; private int port; @Bean @ConditionalOnMissingBean public MongoClient mongoClient() { MongoClientOptions opt = MongoClientOptions.builder().writeConcern(WriteConcern.JOURNALED).build(); System.out.println( ">>> mongo database: " + database ); return new MongoClient(new ServerAddress(host, port), opt); } @Primary @Bean @ConditionalOnSingleCandidate public MongoTemplate mongoTemplate() throws Exception { MongoTemplate my = super.mongoTemplate(); my.setWriteResultChecking(WriteResultChecking.EXCEPTION); return my; } @Bean public MappingMongoConverter mappingMongoConverter() throws Exception { DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(this.mongoDbFactory()); MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, this.mongoMappingContext()); List<Converter<?, ?>> list = new ArrayList<>(); list.add(new LocalTimeReadConverter()); list.add(new LocalTimeWriteConverter()); converter.setCustomConversions(new MongoCustomConversions(list)); converter.setMapKeyDotReplacement("\\[dot\\]"); return converter; } }
测试OK
// 插入一条数据 LocalTime localTime = LocalTime.parse("02:30:40"); ScheduleObject obj = ScheduleObject.createDailySchedule("每隔1小时执行", localTime, new Date(), null, SystemUnitEnum.FAL_POS); posScheduleObjectRepository.save(obj); //读取插入的数据 obj = posScheduleObjectRepository.findAll().get(0);
查看Mongodb中已经存储的数据
{ "_id" : ObjectId("5c2467595e7f2f3a3074713a"), "name" : "每隔1小时执行", "uuid" : "958137ca07574ff1af7401615b8b4fbd", "owner" : "FAL_POS", "recurrence" : "DAILY", "startingDailyAt" : "02:11:20", "startTime" : ISODate("2018-12-27T05:47:02.817+0000"), "endTime" : ISODate("2999-12-30T17:00:00.000+0000"), "createdTime" : ISODate("2018-12-27T05:47:05.470+0000"), "lastModifiedTime" : ISODate("2018-12-27T05:47:05.470+0000"), "version" : NumberLong(0), "_class" : "com.falsec.nsm.common.domain.ScheduleObject" }
使用自定义Json序列化器实现LocalTime的序列化与反序列化
上面讲的是如何将LocalTime存储到数据库,并从数据库中恢复数据到模型。这只是后端到数据库的处理过程,那么要想完整处理LocalTime,还需要实现前端到后端的处理。
接下来我们就将下前端表单提交上来的 10:20:00 字符串格式,如何反序列化成LocalTime,以及如何将 LocalTime序列化成字符串 10:20:00 返回到前端。
LocalTime序列化
默认情况下,jackson序列化带有LocalTime类型字段的实体时,由于LocalTime也是个对象,因此会把它序列化成单独的json,下面的 startingDailyAt属性就是LocalTime类型:
{ "id" : "5c258dd3cd8c7b2938688eb4", "version" : 0, "name" : "test", "recurrence" : "每天", "startTime" : "2018-12-28T11:00:00.000+0800", "endTime" : "2018-12-28T12:00:00.000+0800", "startingDailyAt" : { "hour" : 10, "minute" : 20, "second" : 0, "nano" : 0 } }
实际上,我们希望返回 "startingDailyAt":"10:20:00"
这种格式。
通过@JsonSerialize(using = MyLocalTimeSerializer.class)—推荐
public class FalLocalTimeSerializer extends JsonSerializer<LocalTime> { @Override public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { //String fieldName = gen.getOutputContext().getCurrentName(); gen.writeString(value.toString()); } }
必须在DTO字段上添加注解:
@JsonSerialize(using = LocalTimeSerializer.class) @JsonDeserialize(using = LocalTimeDeserializer.class) private LocalTime startingDailyAt;
返回到前端格式:
{ "startingDailyAt" : "10:20" }
这里顺便说一下,自定义序列化时如果需要返回的startingDailyAt值是对象,就需要使用 gen.writeStartObject()、gen.writeEndObject(),数组同理。
LocalTime反序列化
通过@JsonDeserialize(using = MyLocalTimeDeserializer.class)—推荐
public class LocalTimeDeserializer extends JsonDeserializer<LocalTime> { @Override public LocalTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { String value = jp.getText(); if (StringUtils.isBlank(value)) return null; return LocalTime.parse(value); } }
通过@JsonCreator实现
如果DTO的字段类型是LocalTime,我们使用 @JsonCreator 可以将前端提交上来的 10:20:00字符串转换成LocalTime类型的数据,如下:
@JsonCreator public ScheduleObjectDto(@JsonProperty("startingDailyAt") String startingDailyAt) { if (!StringUtils.isEmpty(startingDailyAt)) { this.startingDailyAt = LocalTime.parse(startingDailyAt); } }
使用@JsonCreator时,我们仅提供一个 @JsonProperty(“startingDailyAt”)参数,意思是说反序列化时,json中的这个属性要用我这里的代码来处理,其他属性如 startTime、endTime等不用这个代码处理,而是和平常一样处理,即其他属性也会反序列化的。