1.概述
书接上回,我们总结了使用Date处理日期时间的存在问题以及讲述了Java 8全新日期时间LocalDate,LocalDateTime等强大功能。可谓是使用LocalDateTime替代Date操作处理日期时间之后:任凭风浪起,稳坐钓鱼台 。但我们都知道大部分开发人员是守旧的,我Date用的好好的,也用了这么多年了,现在要换成Java 8提供的全新LocalDate,LocalDateTime,你说换就换啊???兼容吗?出问题了谁负责? 所以在Spring Boot项目中大家从Date过渡到LocalDateTime最关心以下两个问题:
使用LocalDateTime类型字段作为接口出入参数,能正常映射转换前端传入的参数吗?返回参数前端是否能收到一个正常的日期时间字段值?
使用LocalDateTime类型作为数据库实体类对象字段,能正常写入数据库和读取吗?简单来说就是看数据库能否正常序列化和反序列化LocalDateTime等Java 8提供的全新日期时间类型。
接下来就分别看看这两个困扰在大家心中的问题吧。
2.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;
}
没想到执行结果报错~~~出师不利,具体情况如下所示:
控制台错误信息:不能正常解析转换成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,重新启动项目调用接口发现同样正常输出:
3.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-plus,对该框架不太了解的可以去看看我们之前总结的入门篇:MyBatis-Plus最详细的入门教程 和高级篇:一文带你掌握MyBatis-Plus高级功能点如何使用 依赖如下:请注意我使用的版本
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.9</version>
</dependency>
<!-- mp依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
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这个版本压根没实现转换,所以报错了。所以最好使用高一点的版本
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.9</version>
</dependency>
4.总结
LocalDateTime 在设计上更加现代化、易用且安全,克服了 Date 和 Calendar 的诸多缺点。虽然它们在功能上有重叠之处,但 LocalDateTime 提供了更好的 API 和功能,推荐在 Java 8 及以上版本中使用新的日期和时间 API。结合本文教会你在Spring Boot项目中丝滑从Date过渡到LocalDateTime,你还在担心什么???