【Jackson 填坑日记】“把 CLOB 直接扔给前端?MonitorLock 当场社死”
摘要: 文章探讨了Jackson序列化Oracle CLOB对象时遇到的InvalidDefinitionException问题。问题根因是Jackson尝试序列化JDBC驱动内部对象MonitorLock。作者提出三种解决方案:1) 推荐方案是使用DTO隔离驱动对象,通过类型处理器将CLOB转为String;2) 临时方案是关闭FAIL_ON_EMPTY_BEANS特性;3) 自定义ClobSe
·
【Jackson 填坑日记】“把 CLOB 直接扔给前端?MonitorLock 当场社死”
作者:@Neoest
时间:2025-08-15
关键词:Jackson、oracle.sql.CLOB、InvalidDefinitionException、FAIL_ON_EMPTY_BEANS、DTO、序列化
1. 问题现场
开发新写的「慢 SQL 诊断接口」上线即 500,日志狂飙:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
No serializer found for class oracle.jdbc.internal.Monitor$CloseableLock
and no properties discovered to create BeanSerializer
(through reference chain: java.util.HashMap["rows"]
-> java.util.ArrayList[0]
-> java.util.HashMap["memoryTip"]
-> oracle.sql.CLOB["physicalConnection"]
-> oracle.jdbc.driver.T4CConnection["monitorLock"])
一句话:Jackson 想把 JDBC 驱动内部对象序列化成 JSON,结果被拒绝。
2. 先定位根因
层级 | 对象 | 说明 |
---|---|---|
Controller 返回值 | List<Map<String,Object>> |
直接把 ResultSet → Map |
Map 里的值 | oracle.sql.CLOB |
驱动实现的 CLOB 接口 |
CLOB 内部字段 | T4CConnection |
含 MonitorLock (驱动私有) |
Jackson 默认策略 | 序列化 所有 public getter | 遇到无 getter 的内部类直接抛错 |
3. 错误示范(千万别学)
@GetMapping("/slow-sql")
public List<Map<String, Object>> slowSql() {
return jdbcTemplate.queryForList(sql); // ❌ 直接把 ResultSet → Map
}
4. 三种根治姿势
✅ 方案 A:DTO 收口,禁止裸 Map(推荐)
- 定义前端真正需要的数据结构:
@Data
public class SlowSqlVO {
private Long id;
private String sqlText; // CLOB → String
private Long elapsedMs;
}
- 手动把
CLOB
→String
:
@Mapper
public interface SlowSqlMapper {
@Select("select id, sql_fulltext, elapsed_time from v$sql_monitor")
@Results({
@Result(column = "sql_fulltext", property = "sqlText",
typeHandler = ClobStringTypeHandler.class)
})
List<SlowSqlVO> list();
}
- Jackson 只序列化干净的 DTO,从根源隔离驱动对象。
✅ 方案 B:关闭 FAIL_ON_EMPTY_BEANS
(临时止血)
spring:
jackson:
serialization:
fail-on-empty-beans: false
或在代码里:
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
副作用:
- 只是把异常降级为空对象
{}
,前端仍看不到真实数据 - 驱动内部字段依旧在网络里裸奔,不安全
- 治标不治本,不推荐上线。
✅ 方案 C:自定义 ClobSerializer
+ @JsonSerialize
如果必须保留 Map,可以注册 全局模块:
public class ClobSerializer extends JsonSerializer<CLOB> {
@Override
public void serialize(CLOB value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
try (Reader reader = value.getCharacterStream()) {
gen.writeString(IOUtils.toString(reader));
} catch (SQLException e) {
throw new IOException(e);
}
}
}
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer clobCustomizer() {
return builder -> builder.serializerByType(CLOB.class, new ClobSerializer());
}
}
- 仍然比裸 Map 安全,但不如 DTO 语义清晰。
5. 小结
方案 | 场景 | 侵入性 | 备注 |
---|---|---|---|
DTO + TypeHandler | 所有项目 | 中 | 官方推荐 |
关闭 FAIL_ON_EMPTY_BEANS | 本地调试 | 低 | 临时方案 |
自定义 Serializer | 遗留代码 | 中 | 需维护 |
6. 一键复制版补丁(方案 A)
@Data
public class SlowSqlVO {
private Long id;
private String sqlText;
private Long elapsedMs;
}
// MyBatis-Plus 写法
@Mapper
public interface SlowSqlMapper extends BaseMapper<SlowSqlVO> {
@Select("select id, sql_fulltext, elapsed_time from v$sql_monitor")
@Results({
@Result(column = "sql_fulltext", property = "sqlText",
typeHandler = ClobStringTypeHandler.class)
})
List<SlowSqlVO> list();
}
7. 参考资料
- Jackson 官方 Wiki:Handling Oracle CLOB
- MyBatis-Plus 文档:自定义 TypeHandler
- 《Effective Java》第 3 版——第 51 条:谨慎设计可序列化接口
如果本文帮到你,记得点赞收藏!评论区一起交流更多 ORM 序列化坑位~
更多推荐
所有评论(0)