一、@MappedJdbcTypes 注解核心定义
@MappedJdbcTypes 用于标注自定义 TypeHandler(类型处理器),明确该处理器能处理的数据库 JDBC 类型(如 VARCHAR、INT、DATE、JSON 等)。简单来说:它告诉 MyBatis "这个处理器专门处理数据库中哪些类型的字段",解决了 "同一 Java 类型对应不同数据库 JDBC 类型" 的转换歧义问题。
核心特性
- 作用对象:仅作用于
TypeHandler实现类(继承BaseTypeHandler或实现TypeHandler); - 核心能力:精准匹配数据库 JDBC 类型,支持多类型绑定、默认类型覆盖;
- 依赖前提:需与
@MappedTypes配合使用,且引入 MyBatis 核心依赖(3.1.0+ 版本支持); - 关键补充:MyBatis 内置的
JdbcType枚举涵盖了所有标准数据库类型(如JdbcType.VARCHAR、JdbcType.INTEGER、JdbcType.JSON)。
二、@MappedJdbcTypes 注解核心属性
该注解包含 2 个核心属性,结构清晰:
| 属性名 | 类型 | 核心作用 | 默认值 |
|---|---|---|---|
value |
JdbcType[] | 指定处理器能处理的数据库 JDBC 类型数组 | 空数组 |
includeNullJdbcType |
boolean | 是否包含 JdbcType.NULL(数据库空类型) |
false |
补充说明:
- 单个 JDBC 类型:
@MappedJdbcTypes(JdbcType.VARCHAR);- 多个 JDBC 类型:
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR});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=[黑色, 白色]}
}
五、关键注意事项
- 与 @MappedTypes 的配合 :
@MappedTypes定义 "处理什么 Java 类型",@MappedJdbcTypes定义 "处理什么数据库类型",两者缺一不可;- 若只写
@MappedTypes,MyBatis 会默认匹配所有 JDBC 类型,可能导致转换歧义;
- JdbcType 枚举匹配 :需根据数据库实际字段类型选择正确的
JdbcType(如 MySQL 的TEXT对应JdbcType.LONGVARCHAR,DATETIME对应JdbcType.TIMESTAMP); - 空值处理 :
- 对数值类型(INT、BIGINT),
rs.getInt()会返回 0,需通过rs.wasNull()判断是否为数据库 NULL; - 开启
includeNullJdbcType = true可避免 NULL 值转换时的异常;
- 对数值类型(INT、BIGINT),
- 优先级规则 :
- SQL 中手动指定
jdbcType(#{status,jdbcType=VARCHAR})>@MappedJdbcTypes注解 > MyBatis 内置匹配;
- SQL 中手动指定
- 数据库兼容性 :不同数据库的 JDBC 类型别名可能不同(如 PostgreSQL 的
TEXT对应JdbcType.TEXT),需适配数据库类型。
总结
@MappedJdbcTypes是 MyBatis 注解,用于绑定自定义类型处理器与数据库 JDBC 类型,解决 "同一 Java 类型对应多数据库类型" 的转换歧义;- 核心应用场景是多 JDBC 类型区分 (如枚举 ↔ VARCHAR/INT)、复杂数据库类型转换(如 JSON、TIMESTAMP);
- 使用时需与
@MappedTypes配合,明确 Java 类型和 JDBC 类型的映射关系,注意空值处理和数据库类型兼容性。