当前位置: 首页 > news >正文

mybatis使用typeHandler实现类型转换

使用mybatis作为操作数据库的orm框架,操作基本数据类型时可以通过内置的类型处理器完成java数据类型和数据库类型的转换,但是对于扩展的数据类型要实现与数据库类型的转换就需要自定义类型转换器完成,比如某个实体类型存储到数据库,可以转换为json字符串存储,读取数据时再转换为对应的实体类。
在mybatis中可以有两种方式实现上面的方案:
一、直接继承mybatis框架提供的 org.apache.ibatis.type.BaseTypeHandler 完成数据类型转换;
二、如果项目引入了mybatis-plus,也可以继承 com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler 实现数据类型转换。
接下来分别介绍上面两种方案的实现方式。
首先在数据库中创建一个表用于测试数据存取:

CREATE TABLE `demo_data`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `detail` json NULL,
  `create_time` datetime NULL,
  PRIMARY KEY (`id`)
);
一、mybatis框架实现类型转换

使用mybatis实现类型转换,首先要自定义一个handler继承自基础的handler,再将自定义的handler注入到字段的typeHandler中就实现了类型转换:

自定义handler:

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @Author xingo
 * @Date 2025/2/6
 */
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
public class DetailTypeHandler extends BaseTypeHandler<DemoData.DetailInfo> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, DemoData.DetailInfo parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, JacksonUtils.toJSONString(parameter));
    }

    @Override
    public DemoData.DetailInfo getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return JacksonUtils.parseObject(rs.getString(columnName), DemoData.DetailInfo.class);
    }

    @Override
    public DemoData.DetailInfo getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return JacksonUtils.parseObject(rs.getString(columnIndex), DemoData.DetailInfo.class);
    }

    @Override
    public DemoData.DetailInfo getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return JacksonUtils.parseObject(cs.getString(columnIndex), DemoData.DetailInfo.class);
    }
}

上面就实现了java类型与数据库类型的对应关系,就是将实体类中的java对象与数据库中的字符串类型自动转换:

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @Author xingo
 * @Date 2025/2/6
 */
@Data
public class DemoData implements Serializable {

    private Integer id;

    private DemoData.DetailInfo detail;

    private LocalDateTime createTime;

    @Data
    public static class DetailInfo implements Serializable {

        private String name;

        private Integer age;

        private LocalDateTime dateTime;
    }
}

接下来就是在编写的xml文件中将刚刚自定义的handler和实体类信息完成对应关系:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xingo.demo.DemoDataMapper">

    <resultMap id="demoData" type="org.xingo.demo.DemoData">
        <id property="id" column="id"/>
        <result property="detail" column="detail" typeHandler="org.xingo.demo.DetailTypeHandler"/>
        <result property="createTime" column="create_time"/>
    </resultMap>

    <insert id="insertDemoData" useGeneratedKeys="true" keyProperty="id">
        insert into demo_data(detail, create_time)
        values (#{detail, typeHandler=org.xingo.demo.DetailTypeHandler}, #{createTime})
    </insert>

    <update id="updateDemoData">
        update demo_data
        set detail=#{detail, typeHandler=org.xingo.demo.DetailTypeHandler}
            create_time=#{createTime}
        where id=#{id}
    </update>

    <select id="findDemoDataById" resultMap="demoData">
        select *
        from demo_data
        where id=#{id}
    </select>
</mapper>

xml对应的接口:

import org.xingo.demo.DemoData;

/**
 * @Author xingo
 * @Date 2025/2/6
 */
public interface DemoDataMapper {

    void insertDemoData(DemoData data);

    void updateDemoData(DemoData data);

    DemoData findDemoDataById(Integer id);
}

上面的几步就实现了自定义数据类型与数据库中字符串类型的转换,测试上面接口可以完成数据的存取:请添加图片描述

二、mybatis-plus框架实现类型转换

使用mybatis实现自定义类型与数据库类型的转换相对来说还是有一点繁琐,如果在项目中引入了mybatis-plus,那么就可以减少xml文件的编写,直接在实体类的字段上添加注解完成xml文件的内容。

使用mybatis-plus实现类型转换首先也是自定义handler类:

import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

/**
 * @Author xingo
 * @Date 2025/2/6
 */
@MappedTypes({DemoData.DetailInfo.class})
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
public class DetailTypeHandler extends AbstractJsonTypeHandler<DemoData.DetailInfo> {

    @Override
    protected DemoData.DetailInfo parse(String json) {
        return JacksonUtils.parseObject(json, DemoData.DetailInfo.class);
    }

    @Override
    protected String toJson(DemoData.DetailInfo detail) {
        return JacksonUtils.toJSONString(detail);
    }
}

映射主要是通过实体类的注解完成的:

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @Author xingo
 * @Date 2025/2/6
 */
@Data
@TableName(value = "demo_data", autoResultMap = true)
public class DemoData implements Serializable {

    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField(typeHandler = DetailTypeHandler.class)
    private DemoData.DetailInfo detail;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @Data
    public static class DetailInfo implements Serializable {

        private String name;

        private Integer age;

        private LocalDateTime dateTime;
    }
}

mapper接口只需要继承mybatis-plus提供的基础mapper就可以:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * @Author xingo
 * @Date 2025/2/6
 */
public interface DemoDataMapper extends BaseMapper<DemoData> {
}

通过上面的定义,所有基于mybatis-plus提供的增改查操作都可以完成字段类型转换。请添加图片描述
测试上面的内容在数据库中产生的数据:
请添加图片描述

附:jackson工具类

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

/**
 * json工具
 *
 * @Author xingo
 * @Date 2025/2/6
 */
@Slf4j
public class JacksonUtils {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    static {
        // Long类型处理,避免前端处理长整型时精度丢失
        SimpleModule module1 = new SimpleModule();
        module1.addSerializer(Long.class, ToStringSerializer.instance);
        module1.addSerializer(Long.TYPE, ToStringSerializer.instance);

        JavaTimeModule module2 = new JavaTimeModule();
        // java8日期处理
        module2.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module2.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        module2.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        module2.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module2.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        module2.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

        OBJECT_MAPPER
                // 添加modules
                .registerModules(module1, module2, new Jdk8Module())
                // 日期类型不转换为时间戳
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
                .configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
                // 反序列化的时候如果多了其他属性,不抛出异常
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                // 如果是空对象的时候,不抛异常
                .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
                // 空对象不序列化
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                // 日期格式化
                .setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
                // 设置时区
                .setTimeZone(TimeZone.getTimeZone("GMT+8"))
                // 驼峰转下划线
                // .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
                // 语言
                .setLocale(Locale.SIMPLIFIED_CHINESE);
    }

    /**
     * 反序列化对象
     */
    public static <T> T parseObject(String json, Class<T> clazz) {
        if (json == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反序列化对象
     */
    public static JsonNode parseObject(String json) {
        if (json == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readTree(json);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反序列化对象
     */
    public static <T> T parseObject(String json, TypeReference<T> type) {
        if (json == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(json, type);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反序列化对象
     */
    public static <T> T parseObject(byte[] bytes, TypeReference<T> type) {
        if (bytes == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(bytes, type);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反序列化对象
     */
    public static <T> T parseObject(JsonNode jsonNode, Class<T> clazz) {
        return jsonNode == null ? null : OBJECT_MAPPER.convertValue(jsonNode, clazz);
    }

    /**
     * 反序列化列表
     */
    public static <T> List<T> parseArray(String json, Class<T> clazz) {
        if (json == null) {
            return null;
        }
        try {
            JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz);
            return OBJECT_MAPPER.treeToValue(OBJECT_MAPPER.readTree(json), javaType);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反序列化列表
     */
    public static <T> List<T> parseArray(JsonNode json, Class<T> clazz) {
        try {
            JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz);
            return json == null ? null : OBJECT_MAPPER.treeToValue(json, javaType);
        } catch (JsonProcessingException e) {
            log.warn(e.getLocalizedMessage());
            return null;
        }
    }

    /**
     * 写为json串
     */
    public static String toJSONString(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof String) {
            return (String) obj;
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 写为字节数组
     */
    public static byte[] toJSONBytes(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsBytes(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取jackson对象
     */
    public static ObjectMapper getObjectMapper() {
        return OBJECT_MAPPER;
    }

    /**
     * 美化输出json格式
     */
    public static String pretty(String json) throws IOException {
        return StringUtils.isBlank(json) ? json : pretty(JacksonUtils.getObjectMapper().readTree(json));
    }

    /**
     * 美化输出json格式
     */
    public static String pretty(JsonNode jsonNode) throws IOException {
        return null == jsonNode ? "" : JacksonUtils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
    }

    /**
     * 对象转json
     */
    public static JsonNode toJsonNode(Object obj) {
        if (obj instanceof String) {
            return parseObject((String) obj, JsonNode.class);
        }
        return obj == null ? null : OBJECT_MAPPER.convertValue(obj, JsonNode.class);
    }
}

相关文章:

  • elabradio入门第三讲——PSK传输系统的载波同步
  • maya创建文字模型
  • 深入理解 Java 反射机制:获取类信息与动态操作
  • Windows 环境下 Grafana 安装指南
  • C++接口继承和实现继承
  • 观察者模式原理详解以及Spring源码如何使用观察者模式?
  • ChatGLM
  • 前端函数在开发环境与生产环境中处理空字符串的差异及解决方案
  • 为什么WP建站更适合于谷歌SEO优化?
  • Mathtype安装入门指南
  • WPF9-数据绑定进阶
  • 基于 GEE 的 2019 - 2024 年研究区大气污染物浓度月度变化趋势(CO、NO₂、SO₂、O₃ 、HCHO)
  • Linux中的权限问题(二)
  • 压力传感器
  • L1-043 阅览室
  • 【基础架构篇六】《DeepSeek显存管理黑科技:OOM错误终极解决方案》
  • JUC并发一
  • vue3 + thinkphp 接入 七牛云 DeepSeek-R1/V3 流式调用和非流式调用
  • 怎麼防止爬蟲IP被網站封鎖?
  • rustdesk编译修改名字
  • “影像上海”中的自媒体影像特展:无论何时,影像都需要空间
  • “毛茸茸”的画,诗意、温暖又治愈
  • 北外滩集团21.6亿元摘上海虹口地块,为《酱园弄》取景地
  • 欧盟公布对美关税反制清单,瞄准美国飞机等产品
  • 盖茨:20年内将捐出几乎全部财富,盖茨基金会2045年关闭
  • 两部门发布山洪灾害气象预警:北京西部、河北西部等局地山洪可能性较大