Hibernate过滤器对Update查询不生效的问题咨询
我在应用中要求所有SELECT/INSERT/UPDATE/DELETE查询都必须附加过滤条件columnABC=:inputValue,所有表都包含columnABC列。目前已经通过package-info定义了全局FilterDef,并在实体上添加了@Filter注解,过滤器对SELECT查询生效,但执行save生成的Update语句时,过滤条件没有被附加(生成的Update语句仅包含主键条件),需要让过滤器也作用于Update查询的方案。
方案1:开启Hibernate过滤器的DML支持
Hibernate默认的过滤器只对SELECT语句生效,要让它覆盖UPDATE/DELETE这类DML操作,需要在过滤器定义和实体注解中添加dml=true参数:
- 修改
package-info.java中的@FilterDef:
@FilterDef( name = "columnFilter", parameters = @ParamDef(name = "columnABC", type = "int"), defaultCondition = "columnABC = :columnABC", dml = true // 新增此参数,启用DML过滤 ) package com.example.entities; import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.ParamDef;
- 实体类上的
@Filter也需要同步配置:
@Filter(name = "columnFilter", dml = true) // 开启DML过滤 @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Table(name = "table1") public class Entity1 implements Serializable { // ... 其他实体字段 @Column(name = "columnABC") private Integer columnABC; }
注意:这个配置会让过滤器自动附加到当前Session下的所有UPDATE/DELETE操作,但INSERT操作无法通过过滤器控制(毕竟INSERT时还没生成对应数据的columnABC值,需要手动设置)。
方案2:自定义Repository更新方法,手动拼接过滤条件
如果不想全局启用DML过滤,可以在Repository里写自定义的更新方法,用JPQL手动拼接过滤条件,替代默认的save操作:
比如在Repository1中添加自定义更新方法:
@Component public class Repository1 { @PersistenceContext private EntityManager entityManager; public void setFilter(Integer columnABC) { Session session = entityManager.unwrap(Session.class); Filter filter = session.enableFilter("columnFilter"); filter.setParameter("columnABC", columnABC); } // 自定义更新方法,强制添加过滤条件 public void updateEntity1(Entity1 entity, Integer columnABC) { String jpql = "UPDATE Entity1 e SET e.column1 = :column1, e.column2 = :column2 " + "WHERE e.id = :id AND e.columnABC = :columnABC"; entityManager.createQuery(jpql) .setParameter("column1", entity.getColumn1()) .setParameter("column2", entity.getColumn2()) .setParameter("id", entity.getId()) .setParameter("columnABC", columnABC) .executeUpdate(); } }
调用时直接用这个自定义方法,就能确保过滤条件被加到UPDATE语句里。
方案3:用拦截器或事件监听器控制DML操作
方法A:Hibernate拦截器修改SQL
自定义一个拦截器,在SQL生成后自动追加过滤条件:
public class ColumnABCFilterInterceptor extends EmptyInterceptor { private Integer columnABC; public void setColumnABC(Integer columnABC) { this.columnABC = columnABC; } @Override public String onPrepareStatement(String sql) { // 只处理UPDATE和DELETE语句 if (columnABC != null && (sql.startsWith("UPDATE") || sql.startsWith("DELETE"))) { int whereIndex = sql.indexOf("WHERE"); if (whereIndex != -1) { // 在原有WHERE条件后追加过滤条件 return sql.substring(0, whereIndex + 6) + " columnABC = " + columnABC + " AND " + sql.substring(whereIndex + 6); } else { // 没有WHERE子句的话直接添加 return sql + " WHERE columnABC = " + columnABC; } } return super.onPrepareStatement(sql); } }
然后在Session中注册这个拦截器:
public void setFilter(Integer columnABC) { Session session = entityManager.unwrap(Session.class); ColumnABCFilterInterceptor interceptor = new ColumnABCFilterInterceptor(); interceptor.setColumnABC(columnABC); session.setInterceptor(interceptor); // 保留原有过滤器用于SELECT查询 Filter filter = session.enableFilter("columnFilter"); filter.setParameter("columnABC", columnABC); }
方法B:JPA实体监听器做前置校验
如果更倾向于通过权限校验来阻止非法更新,可以用JPA的@PreUpdate注解:
@EntityListener(Entity1Listener.class) @Entity // ... 其他实体注解 public class Entity1 implements Serializable { // ... 实体字段 } public class Entity1Listener { // 用ThreadLocal存储当前请求的columnABC值(需要提前在请求上下文里设置) @Autowired private ThreadLocal<Integer> columnABCHolder; @PreUpdate public void preUpdate(Entity1 entity) { Integer currentColumnABC = columnABCHolder.get(); if (!currentColumnABC.equals(entity.getColumnABC())) { throw new SecurityException("无权更新该数据"); } } }
这种方式不会修改SQL,而是在更新前校验数据的columnABC是否符合当前上下文的过滤值,不符合就抛出异常阻止操作。
内容的提问来源于stack exchange,提问作者priya

