SQL Server JDBC读取Latin1字段0x8F字符转UTF-8结果异常
问题原因
该问题并非JDBC驱动逻辑缺陷,核心原因是SQL Server排序规则绑定的代码页映射规则与你的预期不符:
- 你使用的
SQL_Latin1_General_CP1_CS_AS排序规则关联的代码页是Windows-1252(CP1252),它和常被混淆的ISO-8859-1编码在0x80~0x9F字节范围的映射规则存在本质差异:ISO-8859-1中0x8F对应U+008F控制字符,但CP1252规范中0x8F属于未定义的无效字节,没有匹配的Unicode字符。 - 无论使用官方mssql驱动还是jTDS驱动,读取
varchar(非Unicode)类型字段时,都会严格遵循字段排序规则对应的代码页做字节到Unicode的解码:遇到无有效映射的0x8F字节时,会直接替换为Unicode替换字符U+FFFD。当你调用resultSet.getString()拿到字符串对象时,原始字节信息已经丢失,后续再转UTF-8自然得到efbfbd,这和你直接在Java中定义"\u008f"再转码的场景逻辑完全不同——后者从一开始就没有经过代码页解码的丢码环节。 - 你在SQL Server 2017、2019以及两类驱动环境下都能复现问题,恰好说明这是SQL Server本身的代码页规则导致的,和驱动版本、数据库大版本无关。
验证方式
你可以直接在SQL Server客户端执行如下T-SQL,不需要经过JDBC就能看到数据库引擎本身的映射结果:
SELECT CONVERT(nvarchar(10), CONVERT(varchar(10), 0x8F));
执行返回的结果就是U+FFFD,证明数据库层面就没有把该排序规则下的0x8F字节映射为U+008F。
修复方案
- 最彻底的方案是将字段类型从
varchar(35)改为nvarchar(35),直接使用Unicode类型存储数据,从根源上避免代码页映射导致的字符丢失问题。 - 如果暂时无法修改字段类型,查询时不要直接调用
getString()读取,改为读取原始字节后自行按ISO-8859-1编码解码,即可得到正确的U+008F字符:
byte[] fieldRawBytes = resultSet.getBytes(1); String correctStr = new String(fieldRawBytes, StandardCharsets.ISO_8859_1); // 此时调用convertStrToHex(correctStr)即可得到预期的C28F
- 注意不要直接在JDBC连接串全局修改默认字符集为ISO-8859-1,否则会导致同库下其他按CP1252规则存储的0x80~0x9F范围有效字符(比如存储为0x80的欧元符号€)出现解码错误。
内容的提问来源于stack exchange,提问作者Sergo Akopdjanov
相关产品推荐
相关产品推荐

