Hibernate查询MySQL报binary转utf8mb3转换失败问题求助
问题根因
报错里的\xAC\xED\x00\x05是Java原生序列化流的固定魔数,出现这个错误是两个问题叠加导致:
- JDBC连接串没有显式指定字符集,驱动建立连接时自动协商字符集出现偏差,触发了二进制类型到utf8mb3的隐式转换逻辑
- UNION查询关联的表字符集不统一:你只确认了
employee_tracking表是utf8mb4,但没有检查关联的contract、contract_extension表的字符集,UNION合并结果集时会选择两部分查询里优先级最低的字符集(也就是utf8mb3)做统一转换,加上Hibernate原生SQL映射时如果识别到列类型不匹配,会把字段值序列化后传输,刚好命中字符集转换逻辑抛出错误。
排查步骤
- 先查两张关联表的字符集,执行以下SQL,大概率其中某张表或者表内的字符串字段是utf8mb3编码:
SELECT T.table_name, CCSA.character_set_name FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA WHERE CCSA.collation_name = T.table_collation AND T.table_schema = "lambda" AND T.table_name IN ('contract', 'contract_extension');
- 检查当前使用的mysql-connector-java版本,8.0.27版本的MySQL服务端如果配套的驱动版本低于8.0.26,存在UNION查询结果集字符集识别错误的已知bug
- 打开Hibernate的SQL打印和参数绑定日志,确认最终生成的SQL语句、绑定参数是否存在异常序列化的情况
解决方案
按以下顺序操作即可解决:
- 修改JDBC连接URL,显式指定连接使用Unicode和utf8编码,修改后连接串如下:
jdbc:mysql://localhost:3306/lambda?jdbcCompliantTruncation=false&serverTimezone=Europe/Berlin&allowPublicKeyRetrieval=true&useSSL=false&zeroDateTimeBehavior=round&characterEncoding=utf8&useUnicode=true
注意:MySQL Connector/J 8.x版本中,characterEncoding=utf8会自动映射为utf8mb4,不需要手动写utf8mb4,否则可能触发参数识别错误
2. 统一数据库、所有关联表的字符集为utf8mb4,排序规则统一用utf8mb4_0900_ai_ci,彻底避免UNION合并时的隐式字符集转换:
-- 修改库级默认字符集 ALTER DATABASE lambda CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; -- 如果上一步查到两张关联表不是utf8mb4,执行以下语句修改 ALTER TABLE contract CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; ALTER TABLE contract_extension CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
- 调整原生SQL写法:去掉
*通配符查询,显式列出所有需要返回的列,保证UNION两部分查询返回的列数量、类型完全一致;如果不需要去重,把UNION换成UNION ALL,减少不必要的排序去重操作带来的类型转换。参考修改后的代码:
StringBuilder sqlQuery = new StringBuilder("SELECT this_.id as id, this_.number_of_hours as number_of_hours, this_.date_start as date_start, this_.date_end as date_end, this_.date_created as date_created, this_.obsolete as obsolete FROM ("); sqlQuery.append(" select tt.id, tt.number_of_hours, tt.date_start, tt.date_end, tt.date_created, tt.obsolete from employee_tracking tt ") .append(" inner join contract c on tt.contract_id=c.id ") .append(" where tt.obsolete = :OBSOLETE "); // 无去重需求时用UNION ALL,性能更好也能减少类型转换问题 sqlQuery.append(" UNION ALL "); sqlQuery.append(" select tt_.id, tt_.number_of_hours, tt_.date_start, tt_.date_end, tt_.date_created, tt_.obsolete from employee_tracking tt_ ") .append(" inner join contract_extension ce_ on tt_.contract_extension_id=ce_.id ") .append(" where tt_.obsolete = :OBSOLETE "); sqlQuery.append(") AS this_ ORDER BY id ASC "); SQLQuery query = getSessionFactory().getCurrentSession().createSQLQuery(sqlQuery.toString()); query.setParameter("OBSOLETE", 0); query.addEntity(EmployeeTracking.class); List lst = query.list();
- 如果以上操作后仍报错,检查
EmployeeTracking实体类:确认所有字段类型和数据库字段类型一一对应,不要出现数据库是varchar/int等基础类型、实体类字段定义为Object/Blob等通用类型的情况,避免Hibernate识别类型失败时对字段做序列化处理。
内容的提问来源于stack exchange,提问作者javaistaucheineinsel
相关产品推荐
相关产品推荐

