You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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更可靠:

  1. 定义基础实体类并添加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
}
  1. 让所有业务实体继承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逻辑:

  1. 实现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,继续执行默认插入逻辑
    }
}
  1. 注册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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 09:51:13