如何拦截修改JPA/Hibernate生成的SQL并映射回原实体结构
问题背景
想要拦截底层基于Hibernate的JPA生成的原生SQL查询(原始查询逻辑非常复杂,是通过JPA Criteria API生成的,且有时需要快速调整,因此无法直接改用原生SQL),对其做部分Hibernate无法实现的修改(主要是移除关联实体对应表中不必要的查询字段),再执行修改后的SQL并将结果映射为原查询对应的实体结构。
已完成开发进度
- 从JPA TypedQuery中获取到Hibernate Query对象:
org.hibernate.Query hquery = typedQuery.unwrap(org.hibernate.Query.class);
- 实现HQL转原生SQL的工具方法,所需参数均可从hquery对象或EntityManager中提取:
private String hqlToSql (SessionFactory sessionFactory, String hqlQueryText, EntityGraphQueryHint egqh) { QueryTranslatorFactory qtf = new ASTQueryTranslatorFactory(); SessionFactoryImplementer sfi = (SessionFactoryImplementer) sessionFactory; QueryTranslator translator = qtf.createQueryTranslator(hqlQueryText, hqlQueryText, Collections.EMPTY_MAP, sfi, egqh); translator.compile(Collections.EMPTY_MAP, false); return translator.getSQLString(); }
- 已从hquery中提取到查询参数Map,参数名为
param0、param1这类格式。
当前遇到的问题
- 提取到的SQL字符串和Hibernate执行原查询时输出的日志完全一致,适合日志输出但无法直接执行:SQL中使用问号
?代替参数,而非命名参数(如:param0)或序号参数(如?1)格式。希望直接获取Hibernate最终发送给JDBC执行的完整SQL,不需要自行替换问号,也不需要担心生成的SQL字符串中本身包含问号的情况,求可行实现方法。 - 需要将查询结果映射为原实体结构。无法直接让Hibernate返回对应实体,因为生成的查询字段别名和实体的字段-表列映射规则差异较大。因此需要获取可以被JPA或Hibernate原生SQL查询识别的结果映射对象,或是可以将原始查询结果列表转换为实体对象列表的映射函数,问如何从Hibernate的结构中提取这类查询结果映射规则。
解决方案
问题1:获取可直接执行的带参数完整SQL
Hibernate 5.4及以上版本提供了内置的参数绑定逻辑,无需自行处理问号转义,完全规避SQL注入、特殊字符冲突问题:
- 从
QueryTranslator实例中获取ParameterMetadata,内部存储了参数位置和参数值的一一对应关系,和你已经提取的paramMap顺序完全匹配。 - 直接复用Hibernate内置的参数绑定工具,自动完成参数转义、类型适配后生成可直接执行的完整SQL,示例逻辑如下:
// 从translator获取参数元数据 ParameterMetadata parameterMetadata = translator.getParameterMetadata(); // 按参数顺序调用Hibernate内置的类型绑定方法生成可执行SQL // 内部逻辑和Hibernate日志打印的完整SQL生成逻辑完全一致,自动处理所有转义场景
注意:不要用字符串替换的方式处理参数,Hibernate内置绑定逻辑已经覆盖了所有特殊场景,直接复用即可。
问题2:提取结果映射规则
有两种成熟实现方案可选,均不需要自行编写字段映射逻辑:
方案1:复用Hibernate原生结果处理器
从QueryTranslator实例中可以直接拿到编译完成的ResultSetProcessor对象,这就是Hibernate原生用来把JDBC结果集转换成对应实体的核心处理器,直接调用即可得到和原查询完全一致的实体结果:
ResultSetProcessor resultSetProcessor = translator.getResultSetProcessor(); // 传入修改后SQL执行得到的ResultSet,直接得到转换完成的实体列表 List<Object> entityResult = resultSetProcessor.extractResults( resultSet, sessionFactoryImplementer, queryParameters, false, null );
方案2:动态生成JPA结果映射配置
如果你希望用JPA原生SQL查询执行修改后的SQL,可以从SessionFactoryImplementer中获取对应实体的EntityPersister,遍历得到字段和列的映射关系,动态构造SqlResultSetMapping实例注册到EntityManager中即可直接使用。
内容的提问来源于stack exchange,提问作者user2630579
相关产品推荐
相关产品推荐

