概叙
实战:早点用JDK8中的java.time来替换java.util.Date-CSDN博客
在Spring Boot
项目中大家从Date
升级到LocalDateTime
最关心以下两个问题:
使用
LocalDateTime
类型字段作为接口出入参数,能正常映射转换前端传入的参数吗?返回参数前端是否能收到一个正常的日期时间字段值?使用
LocalDateTime
类型作为数据库实体类对象字段,能正常写入数据库和读取吗?简单来说就是看数据库能否正常序列化和反序列化LocalDateTime
等Java 8提供的全新日期时间类型。
LocalDateTime
在设计上更加现代化、易用且安全,克服了 Date
和 Calendar
的诸多缺点。虽然它们在功能上有重叠之处,但 LocalDateTime
提供了更好的 API 和功能,推荐在 Java 8 及以上版本中使用新的日期和时间 API。
替换虽然快,但是必要的测试不可少,替换完了记得全网回归测试。
LocalDateTime作为接口出入参数
我也挺好奇的,能不能丝滑地从Date
过渡到LocalDateTime
,先来看看作为接口出入参数与前端交互是什么个情况。话不多说,直接上示例,
先声明一个用户信息参数对象:
@Data
public class UserParam {
private Long id;
private String name;
private LocalDate birthday;
private LocalDateDate createTime;
}
接口调用:这里userDTO
和上面的参数字段一样的,为了看看接口返回结果罢了
@PostMapping("/date")
public UserDTO testLocalDateTime(@RequestBody UserParam param) {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(param, userDTO);
System.out.println(userDTO);
return userDTO;
}
没想到执行结果报错~~~出师不利,具体情况如下所示:HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String "2024-06-30 12:12:56":
控制台错误信息:不能正常解析转换成LocalDateTime
,这真是怕啥来啥哦。。。
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String "2024-06-30 12:12:56": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2024-06-30 12:12:56' could not be parsed at index 10; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "2024-06-30 12:12:56": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2024-06-30 12:12:56' could not be parsed at index 10
接下来说说解决方案:
方案1:在字段属性上加上注解@JsonFormat
格式化日期时间,这种方式简单直接
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
即UserParam
加上之后控制入参格式和UserDTO
加上控制出参格式:
@Data
public class UserParam {
private Long id;
private String name;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
private LocalDate birthday;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
@Data
public class UserDTO {
private Long id;
private String name;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
private LocalDate birthday;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
再次调用上面示例接口就能正常接受参数和返回结果了。
这种方式简单是简单,但就是比较重复不够优雅,一个项目中有这么多接口出入参对象一个个去打上注解不得累个半死,繁琐滴很,能不能一次性搞定,来个全局配置啥的就行那种?肯定有了,下来就来看看。
方案2:全局配置解析LocalDateTime
我们知道Spring Boot
已经为我们提供了日期格式化配置项: spring.jackson.date-format
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
locale: zh_CN
time-zone: GMT+8
default-property-inclusion: non_null
这个配置项我们之前映射Date
也是需要配置的,否则日期解析不成功。所以我们只需要在配置类读取该配置项对LocalDateTime
的格式化转换即可:
@Configuration
public class LocalDateTimeSerializerConfig {
@Bean
public LocalDateTimeSerializer localDateTimeSerializer(JacksonProperties properties) {
String dateFormat = properties.getDateFormat();
if (StringUtils.isBlank(dateFormat)) {
dateFormat = "yyyy-MM-dd HH:mm:ss";
}
return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateFormat));
}
@Bean
public LocalDateTimeDeserializer localDateTimeDeserializer(JacksonProperties properties) {
String dateFormat = properties.getDateFormat();
if (StringUtils.isBlank(dateFormat)) {
dateFormat = "yyyy-MM-dd HH:mm:ss";
}
return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateFormat));
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(
LocalDateTimeSerializer localDateTimeSerializer, LocalDateTimeDeserializer localDateTimeDeserializer) {
return builder -> {
// 序列化
builder.serializerByType(LocalDateTime.class, localDateTimeSerializer);
// 反序列化
builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer);
};
}
}
上面出入参对象去掉注解@JsonFormat
,重新启动项目调用接口发现同样正常输出:
LocalDateTime作为实体类字段正常写入数据库和读取
上面解决了作为接口出入参数映射转换问题,现在来看看作为数据库实体类字段能否正常写入和读取。
实体类user
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "tb_user")
@ExcelIgnoreUnannotated
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userNo;
private Integer gender;
private String name;
private LocalDate birthday;
private String phone;
private String email;
private Integer isDelete;
private String address;
private LocalDateTime createTime;
}
这里我们使用orm框架是:mybatis
public interface UserDAO extends BaseMapper<User> {
}
构建单元测试用例如下:
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceImplTest {
@Resource
private UserDAO userDAO;
@Test
public void testWriteLocalDateTime() {
User user = User.builder().id(1L).userNo("001").gender(0).name("张三").phone("12234")
.birthday(LocalDate.now()).createTime(LocalDateTime.now()).build();
userDAO.insert(user);
}
}
正常写入数据库,开心吧。下面来看看读取:
@Test
public void testReadLocalDateTime() {
User user = userDAO.selectById(1L);
System.out.println(user);
}
控制台打印如下:
User(id=1, userNo=001, gender=0, name=张三, birthday=2024-07-03, phone=12234, email=null, isDelete=0, address=null, createTime=2024-07-03T16:09:12)
完美哈。一切都是那么顺风又顺水。那下面我们来看看出问题的情况,把上面的依赖版本改改:
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<!-- mp依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
再次执行测试用例:
@Test
public void testReadLocalDateTime() {
User user = userDAO.selectById(1L);
System.out.println(user);
}
控制台报错了:
org.springframework.dao.TransientDataAccessResourceException: Error attempting to get column 'birthday' from result set. Cause: java.sql.SQLException: Conversion not supported for type java.time.LocalDate
; Conversion not supported for type java.time.LocalDate; nested exception is java.sql.SQLException: Conversion not supported for type java.time.LocalDate
是的,我们担心的情况出现了,数据库不能正常序列化转换LocalDate了....
这里不兜圈子了直接说原因吧,mybatis-plus3.5.2
是基于mybaits 3.5.10
开发的,mybatis3.5.0
及其之前是支持对LocalDateTime
类型转换,然而从 MyBatis 3.5.1
开始,不再处理 LocalDateTime
(还包括:LocalDate 、 LocalTime
)类型的转换而是交由 JDBC 组件,也就是 mysql-connector-java
来实现,5.1.26
这个版本压根没实现转换,所以报错了。所以最好使用高一点的版本(重点 不建议直接用mybatis-plus;mybatis-plus只是在mybatis的基础上做增强,没有必要图那点方便,mybatis已经够用了
)
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.9</version>
</dependency>