JSON 类型处理
现代数据库(MySQL 5.7+、PostgreSQL、Oracle 12c+)都原生支持 JSON 类型字段。在 MyBatis 中处理 JSON 字段,需要将 Java 对象与 JSON 字符串之间进行双向转换,TypeHandler 是实现这一转换的标准方式。
为什么需要 JSON TypeHandler
数据库存储 JSON 数据:
| id | username | config |
|---|---|---|
| 1 | Alice | {"theme":"dark","lang":"zh","notify":true} |
| 2 | Bob | {"theme":"light","lang":"en","notify":false} |
Java 中对应的对象:
Java
public class UserConfig {
private String theme;
private String lang;
private boolean notify;
// getter/setter
}
没有 TypeHandler 时,需要手动序列化和反序列化:
Java
// 写入时需要手动转 JSON 字符串
UserConfig config = new UserConfig("dark", "zh", true);
String jsonStr = objectMapper.writeValueAsString(config);
// 然后设置到 SQL 参数中
// 读取时需要手动解析
String jsonStr = rs.getString("config");
UserConfig config = objectMapper.readValue(jsonStr, UserConfig.class);
有了 TypeHandler,MyBatis 自动完成转换,Java 代码中直接使用对象。
Jackson 通用 JSON TypeHandler
使用泛型实现通用的 JSON 处理器,支持任意 Java 类型:
Java
@MappedJdbcTypes({JdbcType.VARCHAR})
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final Class<T> type;
public JsonTypeHandler(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
T parameter, JdbcType jdbcType) throws SQLException {
try {
ps.setString(i, MAPPER.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("JSON 序列化失败: " + type.getName(), e);
}
}
@Override
public T getNullableResult(ResultSet rs,
String columnName) throws SQLException {
return parseJson(rs.getString(columnName));
}
@Override
public T getNullableResult(ResultSet rs,
int columnIndex) throws SQLException {
return parseJson(rs.getString(columnIndex));
}
@Override
public T getNullableResult(CallableStatement cs,
int columnIndex) throws SQLException {
return parseJson(cs.getString(columnIndex));
}
private T parseJson(String json) throws SQLException {
if (json == null || json.isEmpty()) {
return null;
}
try {
return MAPPER.readValue(json, type);
} catch (JsonProcessingException e) {
throw new SQLException("JSON 反序列化失败: " + json, e);
}
}
}
关键设计要点
| 设计点 | 说明 |
|---|---|
| 泛型 T | 支持任意 Java 类型,一个 TypeHandler 通用处理 |
| ObjectMapper 静态复用 | ObjectMapper 是线程安全的,避免每次创建导致性能浪费 |
| null 值处理 | parseJson 方法先判断 null,避免反序列化异常 |
| 异常包装 | 将 JsonProcessingException 包装为 SQLException 向上抛出 |
在 Mapper XML 中使用
方式一:resultMap 中指定 typeHandler
XML
<resultMap id="userResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="config" property="config"
typeHandler="com.example.handler.JsonTypeHandler"/>
</resultMap>
<select id="selectById" resultMap="userResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
方式二:参数中使用 typeHandler
XML
<insert id="insert" parameterType="User">
INSERT INTO user (username, config)
VALUES (#{username}, #{config, typeHandler=com.example.handler.JsonTypeHandler})
</insert>
方式三:通过注解自动注册
为特定类型创建专用的 TypeHandler 并注册:
Java
@MappedTypes({UserConfig.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class UserConfigTypeHandler extends JsonTypeHandler<UserConfig> {
public UserConfigTypeHandler() {
super(UserConfig.class);
}
}
注册后 XML 中无需再写 typeHandler:
XML
<!-- 自动使用 UserConfigTypeHandler 处理 config 字段 -->
<insert id="insert">
INSERT INTO user (username, config)
VALUES (#{username}, #{config})
</insert>
Gson 版本实现
如果项目使用 Gson,实现方式类似:
Java
@MappedJdbcTypes({JdbcType.VARCHAR})
public class GsonTypeHandler<T> extends BaseTypeHandler<T> {
private static final Gson GSON = new Gson();
private final Class<T> type;
public GsonTypeHandler(Class<T> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, GSON.toJson(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName)
throws SQLException {
String json = rs.getString(columnName);
return json == null ? null : GSON.fromJson(json, type);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
String json = rs.getString(columnIndex);
return json == null ? null : GSON.fromJson(json, type);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
String json = cs.getString(columnIndex);
return json == null ? null : GSON.fromJson(json, type);
}
}
PostgreSQL JSONB 类型处理器
PostgreSQL 的 JSON/JSONB 类型需要使用 setObject 方法配合 PGobject:
Java
@MappedTypes({UserConfig.class})
@MappedJdbcTypes({JdbcType.OTHER})
public class PostgresJsonTypeHandler extends BaseTypeHandler<UserConfig> {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
UserConfig config, JdbcType jdbcType) throws SQLException {
try {
PGobject pgObject = new PGobject();
pgObject.setType("jsonb");
pgObject.setValue(MAPPER.writeValueAsString(config));
ps.setObject(i, pgObject);
} catch (JsonProcessingException e) {
throw new SQLException("JSON 序列化失败", e);
} catch (SQLException e) {
throw e;
}
}
@Override
public UserConfig getNullableResult(ResultSet rs,
String columnName) throws SQLException {
return parseJson(rs.getString(columnName));
}
@Override
public UserConfig getNullableResult(ResultSet rs,
int columnIndex) throws SQLException {
return parseJson(rs.getString(columnIndex));
}
@Override
public UserConfig getNullableResult(CallableStatement cs,
int columnIndex) throws SQLException {
return parseJson(cs.getString(columnIndex));
}
private UserConfig parseJson(String json) throws SQLException {
if (json == null) return null;
try {
return MAPPER.readValue(json, UserConfig.class);
} catch (JsonProcessingException e) {
throw new SQLException("JSON 反序列化失败", e);
}
}
}
PostgreSQL 与普通 JSON TypeHandler 的区别:
| 特性 | 通用 JSON TypeHandler | PostgreSQL JSONB TypeHandler |
|---|---|---|
| 写入方法 | ps.setString() | ps.setObject() + PGobject |
| JDBC 类型 | JdbcType.VARCHAR | JdbcType.OTHER |
| PostgreSQL 类型 | TEXT/VARCHAR | JSON/JSONB |
| 类型声明 | 不需要 | 需要指定 pgObject.setType("jsonb") |
泛型注册与 Spring 集成
通用 JsonTypeHandler<T> 无法直接通过注解指定具体类型,需要结合 Spring 配置注册:
Java
// MyBatis 配置类
@Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer typeHandlerCustomizer() {
return configuration -> {
configuration.getTypeHandlerRegistry()
.register(UserConfig.class, JsonTypeHandler.class);
configuration.getTypeHandlerRegistry()
.register(OrderDetail.class, JsonTypeHandler.class);
};
}
}
当需要处理多种 JSON 类型时,推荐方案:为每种具体类型创建专用的 TypeHandler 子类并添加
@MappedTypes注解,这样 MyBatis 可以自动注册,无需额外配置。
要点总结
- JSON TypeHandler 的核心是序列化(写)和反序列化(读)两个方向的双向转换
- 使用泛型实现通用的 JSON 处理器,配合
ObjectMapper静态复用避免性能浪费 - null 值处理是必须,避免对 null 字符串调用反序列化方法导致异常
- Jackson 和 Gson 的实现方式类似,选择项目已有的序列化库即可
- PostgreSQL JSONB 类型需要使用
PGobject配合setObject方法写入 - JSON TypeHandler 注册支持 XML 配置、注解
@MappedTypes、Spring 配置三种方式 - 推荐为每种具体类型创建专用 TypeHandler 子类并添加注解,实现自动注册
📝 发现内容有误?点击此处直接编辑