前言

今天遇上这样的一个情况,

在 MySQL 中,字段的属性为 BigInt ,按道理来说,对应 Java 中的 Long 类型。

但实际上项目中与之对应的 Java对象中的属性的类型是 Date 类型, 直接给我这个废物当头一棒

而且不是一两张表,是比较多的表处于 Date 和 BigInt 混用的情况,

你说要好好用Date就好好用Date,要好好用时间戳就好好用时间戳啊,还混用,类型还不对应,麻了

(别问这个项目怎么出现这种事情的,就是来了人,又走了人,然后填坑)

保持微笑:grinning:(此处口吐芬芳xxxxxx)

一、思考

我想知道出现这种情况,你是如何思考的?

我的思考是,到底是改数据库,还是改程序代码比较好。

但是无论哪一种我都不敢轻举妄动,所以我做的第一步是把数据库和代码备份,确保不会被玩坏。

我也问了同事,他的建议是让我改程序。

但是怎么说勒,我细细比较了改代码和改程序的麻烦程度,改数据表麻烦会少很多,我就在表结构中的Bigint 类型改为 datatime 类型,而且当时我的任务,是只局限于一两张业务表,影响范围不大,引用也不多。

我就兴冲冲的把表结构改了,然后把任务完成了~

等到今天上午,我之前询问的那个同事也遇到这个问题,他就向上面的经理提了一嘴,说时间类型不对,问他标准是哪一种,经理说是时间戳,我心里一凉~,麻了,(此处省略一万句)

听完,我就去把表结构改回来了,此时备份就发生作用了~,还原完数据表后,我就打算去改程序代码了

周一写 bug,bug 改一周

突然他和我聊到,xxx,你知道MybatisPlus,有什么转换的方法吗?

这每一个都要改,太麻烦了,而且业务代码中肯定也用到了,这改起来代价太大了,有没有注解的方式可以解决转换问题。

很浅显的思考,我能够感觉到自己的经验的不足,对于很多偷懒(思考),我还是差的太远了。

二、解决方式

因为用到的 ORM 框架是 MybatisPlus,所以首先找的就是有没有官方的支持。

继而就在官网找到一个字段类型处理器,一看才发现,是学过的东西啊,只怪用的太少,知道的太少啊。

然后根据这个线索继续找,就了解到 MyBatis-Plus 字段类型处理器 TypeHandler

就翻看源码,想用一个东西,最快的方式就是看一下源码的实现

2.1、TypeHandler源码

 public interface TypeHandler<T> {​/*** 入库前的类型转换*/void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;​/*** 得到结果。* 查询后的数据处理*/T getResult(ResultSet rs, String columnName) throws SQLException;​T getResult(ResultSet rs, int columnIndex) throws SQLException;T getResult(CallableStatement cs, int columnIndex) throws SQLException;​}

找到接口,看一下源码中针对已有属性是如何处理,我们仿写一份,达到我们的要求即可啊.

2.2、BaseTypeHandler 源码

有这么多,我们直接看一下 BaseTypeHandler 是什么样的处理逻辑,

一方面 base 吗,基础吗,我们就看看基础是什么样的处理啦,另外一方面他是抽象类吗,说明它其他实现类的基类吗。

 public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {​@Overridepublic void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {if (parameter == null) {if (jdbcType == null) {throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");}try {ps.setNull(i, jdbcType.TYPE_CODE);} catch (SQLException e) {throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "+ "Cause: " + e, e);}} else {try {setNonNullParameter(ps, i, parameter, jdbcType);} catch (Exception e) {throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "+ "Try setting a different JdbcType for this parameter or a different configuration property. "+ "Cause: " + e, e);}}}​@Overridepublic T getResult(ResultSet rs, String columnName) throws SQLException {try {return getNullableResult(rs, columnName);} catch (Exception e) {throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);}}​@Overridepublic T getResult(ResultSet rs, int columnIndex) throws SQLException {try {return getNullableResult(rs, columnIndex);} catch (Exception e) {throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);}}​@Overridepublic T getResult(CallableStatement cs, int columnIndex) throws SQLException {try {return getNullableResult(cs, columnIndex);} catch (Exception e) {throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);}}​// 这里就是设置为 不为 null 时的入库public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;​/*** 获取可为空的结果。*/public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;​public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;​public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;​}

看起来好像很长很多的样子:当我们去掉那些判断,精简一下:

 public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {​@Overridepublic void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {// 设置不为null的参数,进行入库 ,此处是抽象类,下层还有实现类,// 记住这里,待会带你看实现类,你就知道了setNonNullParameter(ps, i, parameter, jdbcType);}​@Overridepublic T getResult(ResultSet rs, String columnName) throws SQLException {//  这里从数据库中获取到数据,然后进行类型的一个设置return getNullableResult(rs, columnName);}​@Overridepublic T getResult(ResultSet rs, int columnIndex) throws SQLException {//这两个抽象方法,给我的感觉是一模一样的,包括下一个也是如此return getNullableResult(rs, columnIndex);}​@Overridepublic T getResult(CallableStatement cs, int columnIndex) throws SQLException {return getNullableResult(cs, columnIndex);}​}

2.3、BigIntegerTypeHandler 源码中的实现类

 public class BigIntegerTypeHandler extends BaseTypeHandler<BigInteger> {​@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, BigInteger parameter, JdbcType jdbcType) throws SQLException {// 这里是转为 BigDecimal ,所以这里就算 setBigDecimal,// 那么我们就可以猜测,它还支持其他的方法,Date的话,那就是setDateps.setBigDecimal(i, new BigDecimal(parameter));}​@Overridepublic BigInteger getNullableResult(ResultSet rs, String columnName) throws SQLException {BigDecimal bigDecimal = rs.getBigDecimal(columnName);// 这里是rs.getBigDecimal ,我们待会去试一下能否getDate就可以了return bigDecimal == null ? null : bigDecimal.toBigInteger();}​// 这两个暂时没有做了解,Debug的时候,断点没有执行到这,后期再补一块的知识// 但是为了以防万一,我们待会也会照着它的方式将代码改成这样@Overridepublic BigInteger getNullableResult(ResultSet rs, int columnIndex) throws SQLException {BigDecimal bigDecimal = rs.getBigDecimal(columnIndex);return bigDecimal == null ? null : bigDecimal.toBigInteger();}​@Overridepublic BigInteger getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {BigDecimal bigDecimal = cs.getBigDecimal(columnIndex);return bigDecimal == null ? null : bigDecimal.toBigInteger();}}

这个实现类,没什么代码,而且就是set、get ,并没有其他的一些处理逻辑什么的。

那么我们也照这样的方式实现一个。

2.4、尝试

先明确目标,我们Mysql 中的字段类型 为 BigInt ,Java程序中的属性类型为 Date ,

所以我们在入库的时候就是要将 Date 类型转化为 Long 进行入库,

在从数据库中取出来的时候,要从 Long 类型转化为 Date 映射到 JavaBean中

我们直接copy上面的代码,然后进行一些更改

 public class MyDateTypeHandler implements TypeHandler<Date>{​/*** 入库前的类型转换 即执行insert、update方法时会执行*/@Overridepublic void setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType) throws SQLException {log.info("setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType)....");log.info("[{}],[{}]",parameter,jdbcType);ps.setLong(i, parameter.getTime());}​/*** 查询后的数据处理* 这就是查询出来,进行映射的时候,会执行这段代码*/@Overridepublic Date getResult(ResultSet rs, String columnName) throws SQLException {log.info("getResult(ResultSet rs, String columnName)....",columnName);return new Date(rs.getLong(columnName));}@Overridepublic Date getResult(ResultSet rs, int columnIndex) throws SQLException {log.info("getResult(ResultSet rs, int columnIndex)....");return new Date(rs.getLong(columnIndex));}@Overridepublic Date getResult(CallableStatement cs, int columnIndex)throws SQLException {log.info("getResult(CallableStatement cs, int columnIndex)....");return cs.getDate(columnIndex);}}

咋一眼好像成功啦,但是我们忽略了一个问题,那些默认的允许进行相互进行类型转换的类,它在程序启动的时候,就会被注册进去了。

而且我们写了这个类,还没有指明给谁使用,怎么使用?

基于此,我写了一个小Demo,希望大家能够弄明白,以后遇上也能够解决一些问题

三、实践案例

3.1、数据库

数据库

 SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;​-- ------------------------------ Table structure for handler_test-- ----------------------------DROP TABLE IF EXISTS `handler_test`;CREATE TABLE `handler_test`  (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`date` bigint(50) NOT NULL COMMENT '存时间戳',PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;​-- ------------------------------ Records of handler_test-- ----------------------------INSERT INTO `handler_test` VALUES (1, '测试数据1', 1659967236);INSERT INTO `handler_test` VALUES (2, '测试数据2', 1659967236);INSERT INTO `handler_test` VALUES (3, '测试插入数据', 1659968162926);INSERT INTO `handler_test` VALUES (4, '测试插入数据', 1659972053771);INSERT INTO `handler_test` VALUES (5, '测试插入数据', 1659972815670);​SET FOREIGN_KEY_CHECKS = 1;

3.2、相关代码

我只贴出了相关的代码,其余代码在源码仓库中有,别慌,家人们

service

 public interface IHandlerTestService extends IService<HandlerTest> {​}

TypeHandler 实现类

 /*** @author Ning zaichun*/@Slf4j@MappedJdbcTypes({JdbcType.BIGINT})  //对应数据库类型@MappedTypes({Date.class})            //java数据类型public class MyDateTypeHandler implements TypeHandler<Date>{​/*** 入库前的类型转换*/@Overridepublic void setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType) throws SQLException {log.info("setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType)....");log.info("[{}],[{}]",parameter,jdbcType);ps.setLong(i, parameter.getTime());}​/*** 查询后的数据处理*/@Overridepublic Date getResult(ResultSet rs, String columnName) throws SQLException {log.info("getResult(ResultSet rs, String columnName)....");log.info("[{}]",columnName);return new Date(rs.getLong(columnName));}@Overridepublic Date getResult(ResultSet rs, int columnIndex) throws SQLException {log.info("getResult(ResultSet rs, int columnIndex)....");return new Date(rs.getLong(columnIndex));}@Overridepublic Date getResult(CallableStatement cs, int columnIndex)throws SQLException {log.info("getResult(CallableStatement cs, int columnIndex)....");return cs.getDate(columnIndex);}​}

实体类的修改,有两点,第一点,需要在实体类上加上

  1. @TableName(value = "handler_test",autoResultMap = true) value 是对应表名,autoResultMap 说的

      是否自动构建 resultMap 并使用,只生效与 mp 自动注入的 method,如果设置 resultMap 则不会进行 resultMap 的自动构建并注入,只适合个别字段 设置了 typeHandler 或 jdbcType 的情况
    
  2. 第二点就是要在需要处理的字段上加上 @TableField(typeHandler = MyDateTypeHandler.class) 注解,class就写我们自己编写 Handler.class即可

 @Data@TableName(value = "handler_test",autoResultMap = true)@EqualsAndHashCode(callSuper = false)public class HandlerTest implements Serializable {​private static final long serialVersionUID = 1L;​private String name;​/*** 存时间戳*/@TableField(typeHandler = MyDateTypeHandler.class)@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date date;}

弄完上述这两点,我们还有一个问题,我之前提到一个注册,虽然我们指定了,也写好了,但实际上,并没有注册到一个存储 TypeHandler 一个 Map 集合中去的,Mybatis 在遇到的时候,仍然是会报错的。

但其实只需要在配置文件中加一行即可, 原谅我这么绕圈子,只是希望说明白这是一步步得来的

type-handlers-package 后面填写的是我们Handler 存放的包路径。

有这一步即可。

3.3、测试

 @RunWith(SpringRunner.class)@SpringBootTest@ContextConfiguration(classes = HandlerApplication.class)public class HandlerServiceTest {​​@AutowiredIHandlerTestService handlerTestService;​@Testpublic void test1(){List<HandlerTest> list = handlerTestService.list();list.forEach(System.out::println);}​@Testpublic void test2(){HandlerTest handlerTest = new HandlerTest();handlerTest.setDate(new Date());handlerTest.setName("测试插入数据");handlerTestService.save(handlerTest);}​}

测试插入

 ==>  Preparing: SELECT name,date FROM handler_test==> Parameters: <==    Columns: name, date<==        Row: 测试数据1, 16599672362022-08-08 23:55:25.854  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659967236<==        Row: 测试数据2, 16599672362022-08-08 23:55:25.855  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659967236<==        Row: 测试插入数据, 16599681629262022-08-08 23:55:25.855  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659968162926<==        Row: 测试插入数据, 16599720537712022-08-08 23:55:25.855  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659972053771<==        Row: 测试插入数据, 16599728156702022-08-08 23:55:25.855  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659972815670<==        Row: 测试插入数据, 16599741068472022-08-08 23:55:25.855  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659974106847<==        Row: 测试插入数据, 16599741255422022-08-08 23:55:25.855  INFO 7368 --- [           main] com.nzc.demo.handler.MyDateTypeHandler   : getResult(ResultSet rs, String columnName)....1659974125542<==      Total: 7Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@145113f]HandlerTest(name=测试数据1, date=Tue Jan 20 13:06:07 CST 1970)HandlerTest(name=测试数据2, date=Tue Jan 20 13:06:07 CST 1970)HandlerTest(name=测试插入数据, date=Mon Aug 08 22:16:02 CST 2022)HandlerTest(name=测试插入数据, date=Mon Aug 08 23:20:53 CST 2022)HandlerTest(name=测试插入数据, date=Mon Aug 08 23:33:35 CST 2022)HandlerTest(name=测试插入数据, date=Mon Aug 08 23:55:06 CST 2022)HandlerTest(name=测试插入数据, date=Mon Aug 08 23:55:25 CST 2022)2022-08-08 23:55:25.863  INFO 7368 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closing ...2022-08-08 23:55:25.869  INFO 7368 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closed

MyBatisPlus中的TypeHandler相关推荐

  1. MyBatisPlus中开启了逻辑删除则更新逻辑字段不再管用

    场景 MyBatisPlus中全局Sql注入器应用_逻辑删除使用MyBatisPlus中全局Sql注入器应用_逻辑删除使用: https://blog.csdn.net/BADAO_LIUMANG_Q ...

  2. MyBatisPlus中全局Sql注入器应用_逻辑删除使用

    场景 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/column/info/37194 MyBatisPlus中自定义全局操作流程: https: ...

  3. MybatisPlus中@TableField注解的使用

    场景 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/column/info/37194 基础搭建: https://blog.csdn.net/B ...

  4. mybatis的简单查询用语句吗_面试官:Mybatis中的TypeHandler你用过吗?

    前言 相信大家用Mybatis这个框架至少一年以上了吧,有没有思考过这样一个问题:数据库有自己的数据类型,Java有自己的数据类型,那么Mybatis是如何把数据库中的类型和Java的数据类型对应的呢 ...

  5. mybatisPlus中的field-strategy(字段更新插入策略):null值插入和更新问题

    目录 mybatisPlus中null值插入和更新问题 实际项目解决方法示例一 实际项目解决方法示例二 field-strategy字段更新插入策略介绍 枚举类FieldStrategy源码 枚举类字 ...

  6. mybatisPlus中getOne方法如何只取其中一条数据(Wrapper有多条数据时)

    mybatis-plus中getOne方法只能取一条数据,如果取得多条数据会报错,要么换其他方法,要么只选择其中一条,在条件构造器Wrapper结尾加上.last("limit 1" ...

  7. Mybatis-plus中QueryWrapper的使用

    一.QueryWrapper是什么? QueryWrapper就是在使用Mybatis-plus中真实用到的一种技术,也叫作构造器,能简化sql的操作. 二.常用方法总结 1.单表操作 代码如下(示例 ...

  8. MybatisPlus中and和or的使用

    需求 最近自己玩发现MyBatisPlus还是挺好用的,但是忽然发现对于一个持久层框架来说支持拼接复杂的SQL也是一个优势,对一个持久层框架拼接SQL来说,or比and更难拼,所以此处用案例来实现My ...

  9. Mybatis-Plus中的查(select)和 Wrapper条件构造器-详解 V2.0

    V1.0 已更新为 V-2.0  传送门→ (V-2.0)Mybatis-Plus中的查(select)和QueryWrapper条件构造器​​​​​​​ 前言 本文介绍 mybatis-plus 中 ...

  10. MyBatis-Plus中分页插件IPage的使用

    MyBatis-Plus中分页插件IPage的使用 使用步骤: 1.服务层的接口需要继承 IService<实体类> ,定义分页查询方法,其返回值类型是 IPage<实体类> ...

最新文章

  1. graphviz画图
  2. PAT-乙级-1021. 个位数统计 (15)
  3. 关于SQL操作的一些经验
  4. angularjs与server交互
  5. mysql的含义及特点_MySQL——基本概念
  6. 看着手机会让您晕眩吗? 禁用动画
  7. linux内核系列远程拒绝服务漏洞,预警 | Linux 爆“SACK Panic”远程DoS漏洞,大量主机受影响...
  8. 关于Boost库的split函数在不同的编译器下的使用
  9. 关于appium下载安装及环境配置
  10. python 获取麦克风声音_python调用pyaudio使用麦克风录制wav声音文件的教程
  11. MT6757_MT6763处理器资料分享
  12. Reeder for Mac(RSS阅读器)
  13. Package com.myapp signatures do not match the previously installed version
  14. YoLo: You Only Look Once: Unified, Real-Time Object Detection译文
  15. linux终端关闭xmanager,xmanager之linux 解决方法
  16. 解决网易mumu启动失败问题
  17. numpy计算移动平均值
  18. C++STL容器总结
  19. 优酷 html flash播放器,优酷播放器提示没有安装flash插件的解决办法
  20. 【话题】致敬伟大的科学家史蒂芬·霍金,他留下的预言能实现吗?

热门文章

  1. 虚拟机共享文件夹制作|Ubuntu与本机文件共享
  2. ORACLE Subtype
  3. 三步生活法:土豆(Todo),优势,庆祝
  4. python自动加减法_python实现随机加减法生成器
  5. php美颜相机,手机照片美化软件哪个好|美颜相机官方版-官方版
  6. 探索的乐趣(物理笔记)
  7. VSCode 插件Code Runner 中文提示乱码
  8. 网络状态检测的利器 - ss命令
  9. ubuntu双系统时间同步_ubuntu和windows时间不同步的问题解决 双系统安装
  10. HITB | 360议题分享:卫星可欺骗 地震警报可伪造