美文网首页
SpringBoot系列 Mybatis 之自定义类型转换 Ty

SpringBoot系列 Mybatis 之自定义类型转换 Ty

作者: 一灰灰blog | 来源:发表于2021-09-25 21:45 被阅读0次
    image

    SpringBoot系列 Mybatis 之自定义类型转换 TypeHandler

    在使用 mybatis 进行 db 操作的时候,我们经常会干的一件事情就是将 db 中字段映射到 java bean,通常我们使用ResultMap来实现映射,通过这个标签可以指定两者的绑定关系,那么如果 java bean 中的字段类型与 db 中的不一样,应该怎么处理呢?

    如 db 中为 timestamp, 而 java bean 中定义的却是 long

    • 通过BaseTypeHandler来实现自定义的类型转换

    I. 环境准备

    1. 数据库准备

    使用 mysql 作为本文的实例数据库,新增一张表

    CREATE TABLE `money` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
      `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
      `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
      `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `name` (`name`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
    

    2. 项目环境

    本文借助 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

    pom 依赖如下

    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
    

    db 配置信息 application.yml

    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password:
    

    II. 实例演示

    1. entity 定义

    注意上面 case 中的create_atupdate_at的类型都是timestmap,我们定义的 Entity 如下

    @Data
    public class MoneyPo {
        private Integer id;
    
        private String name;
    
        private Long money;
    
        private Integer isDeleted;
    
        private Timestamp createAt;
    
        private Long updateAt;
    }
    

    2. Mapper 测试接口

    定义一个简单的查询接口,这里直接使用注解的方式(至于 xml 的写法差别也不大)

    /**
     * 主键查询
     *
     * @param id id
     * @return {@link MoneyPo}
     */
    @Select("select * from money where id = #{id}")
    @Results(id = "moneyResultMap", value = {
            @Result(property = "id", column = "id", id = true, jdbcType = JdbcType.INTEGER),
            @Result(property = "name", column = "name", jdbcType = JdbcType.VARCHAR),
            @Result(property = "money", column = "money", jdbcType = JdbcType.INTEGER),
            @Result(property = "isDeleted", column = "is_deleted", jdbcType = JdbcType.TINYINT),
            @Result(property = "createAt", column = "create_at", jdbcType = JdbcType.TIMESTAMP),
    //            @Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP)})
            @Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)})
    MoneyPo getById(@Param("id") int id);
    
    // 关于 SelectProvider 的使用,后面再说,主要是动态sql的演示
    @SelectProvider(type = MoneyService.class, method = "getByIdSql")
    @ResultMap(value = "moneyResultMap")
    MoneyPo getByIdForProvider(@Param("id") int id);
    

    说明:

    • @Results: 这个注解与 ResultMap 标签效果一致,主要用于定义 db 的字段与 java bean 的映射关系
    • id = "moneyResultMap" 这个 id 定义,可以实现@Results 的复用
    • @Result: 关注下updateAt的 typeHandler,这里指定了自定义的 TypeHandler,来实现JdbcType.TEMSTAMP与 Java Bean 中的 long 的转换

    3. 类型转换

    自定义类型转换,主要是继承BaseTypeHandler类,泛型的类型为 Java Bean 中的类型

    /**
     * 自定义类型转换:将数据库中的日期类型,转换成long类型的时间戳
     *
     * 三种注册方式:
     * 1.直接在 result标签中,指定typeHandler,如@Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)
     * 2.在SqlSessionFactory实例中,注册 在SqlSessionFactory实例中.setTypeHandlers(new Timestamp2LongHandler());
     * 3.xml配置,<typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
     *
     * @author yihui
     * @date 2021/7/7
     */
    @MappedTypes(value = Long.class)
    @MappedJdbcTypes(value = {JdbcType.DATE, JdbcType.TIME, JdbcType.TIMESTAMP})
    public class Timestamp2LongHandler extends BaseTypeHandler<Long> {
    
        /**
         * 将java类型,转换为jdbc类型
         *
         * @param preparedStatement
         * @param i
         * @param aLong             毫秒时间戳
         * @param jdbcType          db字段类型
         * @throws SQLException
         */
        @Override
        public void setNonNullParameter(PreparedStatement preparedStatement, int i, Long aLong, JdbcType jdbcType) throws SQLException {
            if (jdbcType == JdbcType.DATE) {
                preparedStatement.setDate(i, new Date(aLong));
            } else if (jdbcType == JdbcType.TIME) {
                preparedStatement.setTime(i, new Time(aLong));
            } else if (jdbcType == JdbcType.TIMESTAMP) {
                preparedStatement.setTimestamp(i, new Timestamp(aLong));
            }
        }
    
        @Override
        public Long getNullableResult(ResultSet resultSet, String s) throws SQLException {
            return parse2time(resultSet.getObject(s));
        }
    
        @Override
        public Long getNullableResult(ResultSet resultSet, int i) throws SQLException {
            return parse2time(resultSet.getObject(i));
        }
    
        @Override
        public Long getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
            return parse2time(callableStatement.getObject(i));
        }
    
        private Long parse2time(Object value) {
            if (value instanceof Date) {
                return ((Date) value).getTime();
            } else if (value instanceof Time) {
                return ((Time) value).getTime();
            } else if (value instanceof Timestamp) {
                return ((Timestamp) value).getTime();
            }
            return null;
        }
    }
    
    • setNonNullParameter:将 java 类型,转换为 jdbc 类型
    • getNullableResult:将 jdbc 类型转 java 类型

    4. TypeHandler 注册

    我们自己定义一个 TypeHandler 没啥问题,接下来就是需要它生效,一般来讲,有下面几种方式

    4.1 result 标签中指定

    通过 result 标签中的 typeHandler 指定

    使用 xml 的方式如

    <result column="update_at" property="updateAt" jdbcType="TIMESTAMP" typeHandler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
    

    注解@Result 的方式如

    @Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)
    

    4.2 SqlSessionFactory 全局配置

    上面的使用姿势为精确指定,如果我们希望应用到所有的场景,则可以通过SqlSessionFactory来实现

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置,这里使用mybatis注解方式,没有配置xml文件
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
        // 注册typehandler,供全局使用
        bean.setTypeHandlers(new Timestamp2LongHandler());
        return bean.getObject();
    }
    

    4.3 全局 xml 配置

    除上面 case 之外,还有一个就是借助mybatis-config.xml配置文件来注册,如

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//ibatis.apache.org//DTD Config 3.1//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!-- 驼峰下划线格式支持 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    
        <typeHandlers>
            <typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
        </typeHandlers>
    </configuration>
    

    注意,使用上面的配置文件,需要在 SpringBoot 中指定如下配置,否则将不会生效

    mybatis:
      config-location: classpath:mybatis-config.xml
    

    4.4 SpringBoot 配置方式

    springboot 配置文件,可以通过指定type-handlers-package来注册 TypeHandler

    mybatis:
      type-handlers-package: com.git.hui.boot.mybatis.handler
    

    5. 小结

    本文主要介绍 db 中的类型与 java bean 中类型的映射适配策略,主要是通过继承BaseTypeHandler来实现自定义的类型转化

    要使用自定义的 TypeHandler,有全局生效与精确指定两种方式

    • @Result/<result>标签中,通过 typeHandler 指定
    • SqlSessionFactory 全局设置 typeHandler
    • mybatis-config.xml 配置文件设置typeHandlers

    此外本文的配置中,还支持了驼峰与下划线的互转配置,这个也属于常见的配置,通过在mybatis-config中如下配置即可

    <setting name="mapUnderscoreToCamelCase" value="true"/>
    

    接下来问题来了,驼峰可以和下划线互转,那么有办法实现自定义的 name 映射么,如果有知道的小伙伴,请不吝指教

    III. 不能错过的源码和相关知识点

    0. 项目

    mybatis 系列博文

    1. 一灰灰 Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    相关文章

      网友评论

          本文标题:SpringBoot系列 Mybatis 之自定义类型转换 Ty

          本文链接:https://www.haomeiwen.com/subject/kqntbltx.html