Hibernate StatementInspector在@OneToMany关联下失效及SQL注入需求咨询
Hibernate拦截器失效与SQL片段注入问题解答
环境说明
使用Hibernate 6.1 + Spring Boot 3.0.2,已实现GlobalSqlInjectInterceptor作为StatementInspector,通过HibernatePropertiesCustomizer完成全局配置。当前问题:插入包含@OneToMany关联的Question实体时,父实体SQL可被拦截,但子实体SQL无拦截效果,拦截器仅执行一次。
问题1:是否因PreparedStatement复用导致Interceptor仅执行一次?
不是。Hibernate中StatementInspector的调用时机是每次生成待执行的SQL语句时,即便复用PreparedStatement,首次生成对应SQL时也会触发拦截器。子实体SQL未被拦截的常见原因:
- 拦截器未全局生效:检查
HibernatePropertiesCustomizer配置,确认是否为所有SessionFactory正确设置了statementInspector属性。 - 子实体操作属于批量执行:批量插入时Hibernate会复用PreparedStatement,但拦截器仍会在首次生成该批量SQL时触发一次,若子实体SQL完全未被拦截,大概率是拦截器逻辑仅处理了父实体相关SQL,未覆盖子实体。
- 会话上下文问题:若子实体操作在独立会话中执行,但拦截器仅绑定到了父实体所在会话,不过全局配置下不会出现此情况。
问题2:如何在HQL/SQL执行时添加额外SQL片段?
针对SELECT语句添加c_deleted=0(逻辑删除)
推荐用Hibernate Filter实现,比手动拼接SQL更可靠:
- 定义基础实体类并添加Filter注解:
@FilterDef(name = "deletedFilter", parameters = @ParamDef(name = "isDeleted", type = Boolean.class)) @Filter(name = "deletedFilter", condition = "c_deleted = :isDeleted") @MappedSuperclass public class BaseEntity { @Column(name = "c_deleted") private Boolean deleted = false; // 省略getter/setter }
- 让所有业务实体继承
BaseEntity,并在全局配置中启用Filter:
@Component public class HibernateConfig implements HibernatePropertiesCustomizer { @Override public void customize(Map<String, Object> hibernateProperties) { // 启用全局逻辑删除Filter hibernateProperties.put("hibernate.default_filter_deletedFilter", "isDeleted=false"); // 保留原拦截器配置 hibernateProperties.put("hibernate.session_factory.statement_inspector", new GlobalSqlInjectInterceptor()); } }
配置完成后,所有SELECT查询会自动带上c_deleted=0条件,无需手动修改SQL。
针对INSERT语句添加指定内容
方式1:通过StatementInspector拼接SQL
在拦截器的inspect方法中解析SQL并修改,需注意复杂SQL的兼容性:
@Override public String inspect(String sql) { String lowerSql = sql.trim().toLowerCase(); // 处理INSERT语句,示例:添加c_create_time字段 if (lowerSql.startsWith("insert")) { int valuesIdx = lowerSql.indexOf("values("); if (valuesIdx != -1) { String prefix = sql.substring(0, valuesIdx); String suffix = sql.substring(valuesIdx); // 在字段列表和值列表中分别添加内容 prefix = prefix.replace("(", "(c_create_time, "); suffix = suffix.replace("(", "(now(), "); return prefix + suffix; } } return sql; }
注意:复杂SQL(如多表插入、带子查询的插入)需用SQL解析库(如JSqlParser)处理,避免拼接出错。
方式2:用PreInsertEventListener自动填充字段
无需修改SQL,直接在实体插入前设置字段值,更符合Hibernate的ORM逻辑:
- 实现PreInsertEventListener:
public class InsertFillListener implements PreInsertEventListener { @Override public boolean onPreInsert(PreInsertEvent event) { Object entity = event.getEntity(); if (entity instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) entity; LocalDateTime now = LocalDateTime.now(); baseEntity.setCreateTime(now); // 将值同步到Hibernate的状态数组中 String[] propNames = event.getPersister().getPropertyNames(); int idx = Arrays.asList(propNames).indexOf("createTime"); if (idx != -1) { event.getState()[idx] = now; } } return false; // 返回false,继续执行默认插入逻辑 } }
- 注册EventListener到全局配置:
@Component public class HibernateEventListenerConfig implements HibernatePropertiesCustomizer { @Override public void customize(Map<String, Object> hibernateProperties) { Map<String, Object> listeners = new HashMap<>(); listeners.put(EventType.PRE_INSERT.name(), new InsertFillListener()); hibernateProperties.put(AvailableSettings.EVENT_LISTENERS, listeners); } }
内容的提问来源于stack exchange,提问作者Deng
相关产品推荐
相关产品推荐

