Spring Boot项目如何优雅丝滑地从Date过渡到LocalDateTime

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,重新启动项目调用接口发现同样正常输出:

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

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>

关于这个具体详解请看 : 把 MyBatis 替换成 MyBatis-Plus出bug

4.总结

LocalDateTime 在设计上更加现代化、易用且安全,克服了 DateCalendar 的诸多缺点。虽然它们在功能上有重叠之处,但 LocalDateTime 提供了更好的 API 和功能,推荐在 Java 8 及以上版本中使用新的日期和时间 API。结合本文教会你在Spring Boot项目中丝滑从Date过渡到LocalDateTime,你还在担心什么???

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值