Hibernate升级至6.6.0.CR1+后原生查询JSON转义异常求助
问题概述
在hibernate-core版本≤6.6.0.Alpha1时,通过EntityManager.createNativeQuery执行带美元引号($dollar$)的原生插入查询可以正常运行,原SQL如下:
INSERT INTO example_table (geom) VALUES ( ST_SetSRID( ST_GeomFromGeoJSON( $dollar${"type":"Point","coordinates":[-34.4214,-15.9216]}$dollar$ ), 4326 ) ) ON CONFLICT DO NOTHING;
此时Hibernate生成的执行语句与原查询完全一致。但升级到hibernate-core 6.6.0.CR1及以上版本(包括Spring Boot 3.4.x配套的Hibernate版本)后,Hibernate会错误地将JSON部分的双引号转义为多组双引号,生成的SQL变成:
INSERT INTO example_table (geom) VALUES ( ST_SetSRID( ST_GeomFromGeoJSON( $dollar$""""""{type:Point,coordinates:[-34.4214,-15.9216]}$dollar$ ), 4326 ) ) ON CONFLICT DO NOTHING;;
这会直接触发PostgreSQL的「ERROR: unknown GeoJSON type」异常,且所有用美元引号包裹的JSON数据都会受此影响。当前仅能通过替换美元引号为单引号规避,但这并非预期方案。
问题原因
这是Hibernate 6.6.0版本开始的原生查询字符串解析逻辑变更导致的bug:新版本的解析器误将美元引号包裹的内容识别为普通字符串字面量,对其中的双引号执行了不必要的转义操作,破坏了GeoJSON的合法格式。
解决办法
方案1:绕过Hibernate的SQL解析,直接执行JDBC语句
通过unwrap获取底层JDBC对象,跳过Hibernate的字符串解析逻辑,直接执行原生SQL:
String nativeSql = "INSERT INTO example_table (geom) VALUES (ST_SetSRID(ST_GeomFromGeoJSON($dollar${\"type\":\"Point\",\"coordinates\":[-34.4214,-15.9216]}$dollar$), 4326)) ON CONFLICT DO NOTHING;"; EntityManager em = ...; // 获取EntityManager实例 em.unwrap(org.hibernate.Session.class) .createNativeQuery(nativeSql) .unwrap(java.sql.PreparedStatement.class) .executeUpdate();
方案2:使用参数化查询传递JSON内容
将JSON作为参数传入SQL,避免Hibernate解析SQL中的JSON字符串,同时符合安全的参数化查询规范:
String geoJson = "{\"type\":\"Point\",\"coordinates\":[-34.4214,-15.9216]}"; String nativeSql = "INSERT INTO example_table (geom) VALUES (ST_SetSRID(ST_GeomFromGeoJSON(?::jsonb), 4326)) ON CONFLICT DO NOTHING;"; EntityManager em = ...; em.createNativeQuery(nativeSql) .setParameter(1, geoJson) .executeUpdate();
这种方式不仅能解决转义问题,还能避免SQL注入风险,是更推荐的实践方案。
方案3:临时回退Hibernate版本(不推荐)
如果暂时无法调整代码,可以临时回退到hibernate-core 6.6.0.Alpha1版本,但这会错过新版本的功能和安全修复,仅作为临时应急方案。
方案4:提交Bug报告推动官方修复
该问题属于Hibernate的解析逻辑bug,可以在Hibernate官方Issue平台提交复现步骤和版本信息,等待官方发布修复版本。
内容的提问来源于stack exchange,提问作者Eans

