使用转换器读取与存储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等不用这个代码处理,而是和平常一样处理,即其他属性也会反序列化的。