如何将Oracle中存Java对象流的RAW列转换为VARCHAR2明文
解决Oracle中RAW存储Java序列化国家代码转明文VARCHAR2的问题
问题背景
项目里用Oracle的RAW类型存储Java序列化后的国家代码列表,比如['AW']会被存成0xACED0005757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B470200007870000000017400024157。这种方案有两个明显问题:
- 调试时看数据库内容完全不直观,没法直接知道存的是哪些国家代码
- 后续写SQL脚本操作、比较该列时,RAW类型限制太多,不好处理
尝试过直接用UPDATE语句修改失败,因为RAW类型不能直接和字符串用=匹配,比如这条语句根本执行不了:
UPDATE my_table SET country_codes = '[AW]' WHERE country_codes = '0xACED0005757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B470200007870000000017400024157';
需求很明确:把该列改成存储明文字符串(比如VARCHAR2类型),后续会同步调整应用逻辑适配新格式。
转换前数据
| id | country_codes [RAW] |
|---|---|
| 1 | 0xACED0005757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B470200007870000000017400024157 |
| 2 | 0xACED0005757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B47020000787000000001740002444B |
| 3 | 0xACED0005757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B47020000787000000002740002444B7400025553 |
目标转换后数据
| id | country_codes [VARCHAR2(255)] |
|---|---|
| 1 | [AW] |
| 2 | [DK] |
| 3 | [DK, US] |
分步解决方法
1. 新增临时列存明文
先别直接改原列,避免数据丢失,先加一个VARCHAR2类型的临时列:
ALTER TABLE my_table ADD country_codes_txt VARCHAR2(255);
2. 写函数解析RAW序列化数据
Java序列化的RAW数据开头是固定魔数0xACED0005,后面跟着对象类型标识和实际字符串内容。可以写个PL/SQL函数把这些RAW数据转成明文列表:
CREATE OR REPLACE FUNCTION deserialize_country_codes(p_raw RAW) RETURN VARCHAR2 IS v_buffer RAW(32767); v_offset NUMBER := 1; v_list_size NUMBER; v_str_len NUMBER; v_str VARCHAR2(255); v_result VARCHAR2(255) := '['; BEGIN -- 跳过序列化魔数和对象头(前28字节,对应示例里的固定前缀) v_offset := 29; -- 读取列表里的元素个数(4字节无符号整数) v_buffer := UTL_RAW.SUBSTR(p_raw, v_offset, 4); v_list_size := UTL_RAW.CAST_TO_NUMBER(v_buffer); v_offset := v_offset + 4; FOR i IN 1..v_list_size LOOP -- 跳过字符串标识字节('t'对应的十六进制74) v_offset := v_offset + 1; -- 读取字符串长度(2字节) v_buffer := UTL_RAW.SUBSTR(p_raw, v_offset, 2); v_str_len := UTL_RAW.CAST_TO_NUMBER(v_buffer); v_offset := v_offset + 2; -- 读取字符串内容(Java序列化字符串是UTF-16BE,每个字符占2字节) v_str := UTL_RAW.CAST_TO_VARCHAR2(UTL_RAW.SUBSTR(p_raw, v_offset, v_str_len * 2)); v_offset := v_offset + v_str_len * 2; -- 拼接成目标格式 IF i > 1 THEN v_result := v_result || ', '; END IF; v_result := v_result || v_str; END LOOP; v_result := v_result || ']'; RETURN v_result; EXCEPTION WHEN OTHERS THEN RETURN '[]'; -- 解析失败返回空列表格式,避免报错 END; /
3. 更新临时列数据
调用上面的函数,把原RAW列的数据转成明文存入临时列:
UPDATE my_table SET country_codes_txt = deserialize_country_codes(country_codes); COMMIT;
4. 验证转换结果
查一下数据,确认转换后的内容符合预期:
SELECT id, country_codes, country_codes_txt FROM my_table;
5. 替换原列(可选)
如果验证没问题,就可以替换原列了:
- 先把原RAW列重命名做备份:
ALTER TABLE my_table RENAME COLUMN country_codes TO country_codes_raw;
- 再把临时列改成原列名:
ALTER TABLE my_table RENAME COLUMN country_codes_txt TO country_codes;
6. 清理备份列(可选)
如果不需要保留原RAW数据了,可以删除备份列:
ALTER TABLE my_table DROP COLUMN country_codes_raw;
关键注意点
UTL_RAW.CAST_TO_VARCHAR2()负责把RAW字节转成字符串,但要注意Java序列化的字符串是UTF-16BE编码,每个字符占2字节,所以解析时长度要对应- 函数里的偏移量是针对你提供的示例数据结构调的,如果Java序列化配置或版本变了,可能需要微调偏移量
- 转换前务必全量备份数据,避免意外丢失
内容的提问来源于stack exchange,提问作者Nermin
相关产品推荐
相关产品推荐

