@MappedJdbcTypes 注解详解:应用场景与实战示例

一、@MappedJdbcTypes 注解核心定义

@MappedJdbcTypes 用于标注自定义 TypeHandler(类型处理器),明确该处理器能处理的数据库 JDBC 类型(如 VARCHAR、INT、DATE、JSON 等)。简单来说:它告诉 MyBatis "这个处理器专门处理数据库中哪些类型的字段",解决了 "同一 Java 类型对应不同数据库 JDBC 类型" 的转换歧义问题。

核心特性

  • 作用对象:仅作用于 TypeHandler 实现类(继承 BaseTypeHandler 或实现 TypeHandler);
  • 核心能力:精准匹配数据库 JDBC 类型,支持多类型绑定、默认类型覆盖;
  • 依赖前提:需与 @MappedTypes 配合使用,且引入 MyBatis 核心依赖(3.1.0+ 版本支持);
  • 关键补充:MyBatis 内置的 JdbcType 枚举涵盖了所有标准数据库类型(如 JdbcType.VARCHARJdbcType.INTEGERJdbcType.JSON)。

二、@MappedJdbcTypes 注解核心属性

该注解包含 2 个核心属性,结构清晰:

属性名 类型 核心作用 默认值
value JdbcType[] 指定处理器能处理的数据库 JDBC 类型数组 空数组
includeNullJdbcType boolean 是否包含 JdbcType.NULL(数据库空类型) false

补充说明:

  1. 单个 JDBC 类型:@MappedJdbcTypes(JdbcType.VARCHAR)
  2. 多个 JDBC 类型:@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
  3. includeNullJdbcType = true:处理器支持处理数据库中值为 NULL 的字段(避免空值转换异常)。

三、@MappedJdbcTypes 核心应用场景

@MappedJdbcTypes 解决的核心问题是 "类型转换的精准性",常见应用场景如下:

场景 1:同一 Java 类型对应多个数据库 JDBC 类型

比如 Java 枚举类型,既可能对应数据库的 VARCHAR 字段(存储枚举名),也可能对应 INT 字段(存储枚举编码)。通过 @MappedJdbcTypes 区分:

  • 处理 VARCHAR 类型的枚举处理器:@MappedJdbcTypes(JdbcType.VARCHAR)
  • 处理 INT 类型的枚举处理器:@MappedJdbcTypes(JdbcType.INTEGER)

场景 2:自定义复杂数据库类型转换

比如 MySQL 的 JSON 类型(对应 JdbcType.JSON)、PostgreSQL 的 TIMESTAMP 类型(对应 JdbcType.TIMESTAMP),需明确绑定处理器的目标 JDBC 类型,避免 MyBatis 自动匹配错误。

场景 3:覆盖 MyBatis 内置类型转换规则

MyBatis 内置了基础类型的转换规则(如 String ↔ VARCHAR),若需自定义规则(如 String ↔ JSON),可通过 @MappedJdbcTypes(JdbcType.JSON) 明确绑定,优先级高于内置规则。

场景 4:处理数据库空值(NULL)

当数据库字段值为 NULL 时,通过 includeNullJdbcType = true 让处理器接管空值转换,避免 MyBatis 抛出 "无法转换 NULL 类型" 的异常。

四、实战示例代码

示例 1:基础场景 - 枚举 + 多 JDBC 类型绑定

步骤 1:定义枚举(支持两种存储方式)
java 复制代码
package com.example.mybatis.enums;

// 订单状态枚举:支持 VARCHAR(存储名称)/INT(存储编码)两种数据库存储方式
public enum OrderStatus {
    PENDING(1, "待支付"),
    PAID(2, "已支付"),
    CANCELLED(3, "已取消");

    private final int code; // 编码(对应数据库 INT 类型)
    private final String name; // 名称(对应数据库 VARCHAR 类型)

    OrderStatus(int code, String name) {
        this.code = code;
        this.name = name;
    }

    // 从 INT 编码转枚举
    public static OrderStatus fromCode(Integer code) {
        for (OrderStatus status : values()) {
            if (status.code == code) {
                return status;
            }
        }
        throw new IllegalArgumentException("无效的状态编码:" + code);
    }

    // 从 VARCHAR 名称转枚举
    public static OrderStatus fromName(String name) {
        for (OrderStatus status : values()) {
            if (status.name.equals(name)) {
                return status;
            }
        }
        throw new IllegalArgumentException("无效的状态名称:" + name);
    }

    // getter 方法
    public int getCode() { return code; }
    public String getName() { return name; }
}
步骤 2:编写两个处理器,分别绑定不同 JDBC 类型
处理器 1:处理枚举 ↔ VARCHAR(数据库)
java 复制代码
package com.example.mybatis.handler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import com.example.mybatis.enums.OrderStatus;
import java.sql.*;

/**
 * 枚举 ↔ 数据库 VARCHAR 类型转换
 * @MappedTypes:绑定 Java 类型(OrderStatus)
 * @MappedJdbcTypes:绑定数据库 JDBC 类型(VARCHAR),包含 NULL 类型
 */
@MappedTypes(OrderStatus.class)
@MappedJdbcTypes(value = JdbcType.VARCHAR, includeNullJdbcType = true)
public class OrderStatusVarcharHandler extends BaseTypeHandler<OrderStatus> {

    // Java 枚举 → 数据库 VARCHAR(插入/更新)
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, OrderStatus parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.getName()); // 存储枚举名称(如"待支付")
    }

    // 数据库 VARCHAR → Java 枚举(查询:列名)
    @Override
    public OrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String name = rs.getString(columnName);
        return name == null ? null : OrderStatus.fromName(name);
    }

    // 数据库 VARCHAR → Java 枚举(查询:列索引)
    @Override
    public OrderStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String name = rs.getString(columnIndex);
        return name == null ? null : OrderStatus.fromName(name);
    }

    // 数据库 VARCHAR → Java 枚举(存储过程)
    @Override
    public OrderStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String name = cs.getString(columnIndex);
        return name == null ? null : OrderStatus.fromName(name);
    }
}
处理器 2:处理枚举 ↔ INTEGER(数据库)
java 复制代码
package com.example.mybatis.handler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import com.example.mybatis.enums.OrderStatus;
import java.sql.*;

/**
 * 枚举 ↔ 数据库 INTEGER 类型转换
 * @MappedJdbcTypes:绑定数据库 JDBC 类型(INTEGER)
 */
@MappedTypes(OrderStatus.class)
@MappedJdbcTypes(value = JdbcType.INTEGER, includeNullJdbcType = true)
public class OrderStatusIntHandler extends BaseTypeHandler<OrderStatus> {

    // Java 枚举 → 数据库 INTEGER
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, OrderStatus parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode()); // 存储枚举编码(如1)
    }

    // 数据库 INTEGER → Java 枚举
    @Override
    public OrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Integer code = rs.getInt(columnName);
        // 处理 NULL:rs.getInt 返回 0 时,需通过 wasNull() 判断是否为数据库 NULL
        return rs.wasNull() ? null : OrderStatus.fromCode(code);
    }

    @Override
    public OrderStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Integer code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : OrderStatus.fromCode(code);
    }

    @Override
    public OrderStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Integer code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : OrderStatus.fromCode(code);
    }
}
步骤 3:配置扫描路径(关键)

application.yml 中指定类型处理器扫描路径,确保注解生效:

java 复制代码
mybatis:
  type-handlers-package: com.example.mybatis.handler # 扫描所有自定义处理器
  configuration:
    map-underscore-to-camel-case: true # 驼峰命名转换(可选)
步骤 4:定义实体与 Mapper(验证不同 JDBC 类型转换)
数据库表设计(两张表,分别用 VARCHAR/INT 存储枚举)
java 复制代码
-- 表1:用 VARCHAR 存储订单状态
CREATE TABLE t_order_varchar (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(32) NOT NULL,
    status VARCHAR(16) COMMENT '订单状态(待支付/已支付/已取消)'
);

-- 表2:用 INT 存储订单状态
CREATE TABLE t_order_int (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(32) NOT NULL,
    status INT COMMENT '订单状态(1/2/3)'
);
实体类(共用 OrderStatus 枚举)
java 复制代码
package com.example.mybatis.entity;

import com.example.mybatis.enums.OrderStatus;
import lombok.Data;

@Data
public class OrderVarchar {
    private Long id;
    private String orderNo;
    private OrderStatus status; // 对应数据库 VARCHAR 类型
}

@Data
public class OrderInt {
    private Long id;
    private String orderNo;
    private OrderStatus status; // 对应数据库 INT 类型
}
Mapper 接口
java 复制代码
package com.example.mybatis.mapper;

import com.example.mybatis.entity.OrderInt;
import com.example.mybatis.entity.OrderVarchar;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderVarcharMapper {
    @Insert("INSERT INTO t_order_varchar (order_no, status) VALUES (#{orderNo}, #{status})")
    int insert(OrderVarchar order);

    @Select("SELECT id, order_no, status FROM t_order_varchar WHERE id = #{id}")
    OrderVarchar selectById(Long id);
}

@Repository
public interface OrderIntMapper {
    @Insert("INSERT INTO t_order_int (order_no, status) VALUES (#{orderNo}, #{status})")
    int insert(OrderInt order);

    @Select("SELECT id, order_no, status FROM t_order_int WHERE id = #{id}")
    OrderInt selectById(Long id);
}
步骤 5:测试转换效果
java 复制代码
package com.example.mybatis;

import com.example.mybatis.entity.OrderInt;
import com.example.mybatis.entity.OrderVarchar;
import com.example.mybatis.enums.OrderStatus;
import com.example.mybatis.mapper.OrderIntMapper;
import com.example.mybatis.mapper.OrderVarcharMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MappedJdbcTypesTest {

    @Autowired
    private OrderVarcharMapper orderVarcharMapper;
    @Autowired
    private OrderIntMapper orderIntMapper;

    @Test
    public void testDifferentJdbcType() {
        // 1. 测试 VARCHAR 类型转换(存储名称)
        OrderVarchar varcharOrder = new OrderVarchar();
        varcharOrder.setOrderNo("ORDER_V001");
        varcharOrder.setStatus(OrderStatus.PENDING); // 待支付
        orderVarcharMapper.insert(varcharOrder);
        OrderVarchar savedVarchar = orderVarcharMapper.selectById(varcharOrder.getId());
        System.out.println("VARCHAR 类型转换结果:" + savedVarchar.getStatus()); // 输出:PENDING
        System.out.println("数据库存储值:" + savedVarchar.getStatus().getName()); // 输出:待支付

        // 2. 测试 INTEGER 类型转换(存储编码)
        OrderInt intOrder = new OrderInt();
        intOrder.setOrderNo("ORDER_I001");
        intOrder.setStatus(OrderStatus.PAID); // 已支付(编码2)
        orderIntMapper.insert(intOrder);
        OrderInt savedInt = orderIntMapper.selectById(intOrder.getId());
        System.out.println("INTEGER 类型转换结果:" + savedInt.getStatus()); // 输出:PAID
        System.out.println("数据库存储值:" + savedInt.getStatus().getCode()); // 输出:2
    }
}

示例 2:高级场景 - 处理 MySQL JSON 类型

MySQL 5.7+ 支持 JSON 类型(对应 MyBatis 的 JdbcType.JSON),通过 @MappedJdbcTypes 绑定该类型,实现 Java Map 与数据库 JSON 字段的转换:

步骤 1:自定义 JSON 类型处理器
java 复制代码
package com.example.mybatis.handler;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.util.Map;

/**
 * Java Map ↔ MySQL JSON 类型转换
 * @MappedTypes:绑定 Java 类型(Map)
 * @MappedJdbcTypes:绑定数据库 JDBC 类型(JSON)
 */
@MappedTypes(Map.class)
@MappedJdbcTypes(JdbcType.JSON)
public class Map2JsonTypeHandler extends BaseTypeHandler<Map<String, Object>> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    // Map → JSON 字符串(存入数据库)
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) throws SQLException {
        try {
            String json = objectMapper.writeValueAsString(parameter);
            ps.setString(i, json); // MySQL JSON 类型可接收字符串
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Map 转 JSON 失败", e);
        }
    }

    // JSON 字符串 → Map(从数据库读取)
    @Override
    public Map<String, Object> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        return json == null ? null : parseJson(json);
    }

    @Override
    public Map<String, Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        return json == null ? null : parseJson(json);
    }

    @Override
    public Map<String, Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        return json == null ? null : parseJson(json);
    }

    // 封装 JSON 解析逻辑
    private Map<String, Object> parseJson(String json) {
        try {
            return objectMapper.readValue(json, Map.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JSON 转 Map 失败", e);
        }
    }
}
步骤 2:测试 JSON 类型转换
java 复制代码
// 实体类
@Data
public class Product {
    private Long id;
    private String name;
    private Map<String, Object> attributes; // 对应数据库 JSON 字段
}

// Mapper
@Repository
public interface ProductMapper {
    @Insert("INSERT INTO t_product (name, attributes) VALUES (#{name}, #{attributes})")
    int insert(Product product);

    @Select("SELECT id, name, attributes FROM t_product WHERE id = #{id}")
    Product selectById(Long id);
}

// 测试代码
@Test
public void testJsonTypeHandler() {
    Product product = new Product();
    product.setName("手机");
    // 模拟 Map 数据
    Map<String, Object> attrs = new HashMap<>();
    attrs.put("brand", "华为");
    attrs.put("price", 4999);
    attrs.put("colors", Arrays.asList("黑色", "白色"));
    product.setAttributes(attrs);

    productMapper.insert(product);
    Product savedProduct = productMapper.selectById(product.getId());
    System.out.println("JSON 转换结果:" + savedProduct.getAttributes());
    // 输出:{brand=华为, price=4999, colors=[黑色, 白色]}
}

五、关键注意事项

  1. 与 @MappedTypes 的配合
    • @MappedTypes 定义 "处理什么 Java 类型",@MappedJdbcTypes 定义 "处理什么数据库类型",两者缺一不可;
    • 若只写 @MappedTypes,MyBatis 会默认匹配所有 JDBC 类型,可能导致转换歧义;
  2. JdbcType 枚举匹配 :需根据数据库实际字段类型选择正确的 JdbcType(如 MySQL 的 TEXT 对应 JdbcType.LONGVARCHARDATETIME 对应 JdbcType.TIMESTAMP);
  3. 空值处理
    • 对数值类型(INT、BIGINT),rs.getInt() 会返回 0,需通过 rs.wasNull() 判断是否为数据库 NULL;
    • 开启 includeNullJdbcType = true 可避免 NULL 值转换时的异常;
  4. 优先级规则
    • SQL 中手动指定 jdbcType#{status,jdbcType=VARCHAR})> @MappedJdbcTypes 注解 > MyBatis 内置匹配;
  5. 数据库兼容性 :不同数据库的 JDBC 类型别名可能不同(如 PostgreSQL 的 TEXT 对应 JdbcType.TEXT),需适配数据库类型。

总结

  1. @MappedJdbcTypes 是 MyBatis 注解,用于绑定自定义类型处理器与数据库 JDBC 类型,解决 "同一 Java 类型对应多数据库类型" 的转换歧义;
  2. 核心应用场景是多 JDBC 类型区分 (如枚举 ↔ VARCHAR/INT)、复杂数据库类型转换(如 JSON、TIMESTAMP);
  3. 使用时需与 @MappedTypes 配合,明确 Java 类型和 JDBC 类型的映射关系,注意空值处理和数据库类型兼容性。
相关推荐
虾说羊3 小时前
redis中的哨兵机制
数据库·redis·缓存
_F_y3 小时前
MySQL视图
数据库·mysql
2301_790300963 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
3 小时前
java关于内部类
java·开发语言
好好沉淀3 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin3 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder3 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
吨~吨~吨~3 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea
你才是臭弟弟3 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
短剑重铸之日3 小时前
《设计模式》第二篇:单例模式
java·单例模式·设计模式·懒汉式·恶汉式