【问题标题】:Getting table aliases correctly in Hibernate Interceptor在 Hibernate 拦截器中正确获取表别名
【发布时间】:2015-06-30 07:14:19
【问题描述】:

我需要为我的应用程序管理的所有实体添加 软删除 功能。为了实现这一点,我创建了一个带有 deleted 字段的基实体类(MappedSuperclass),如下所示:

@MappedSuperclass
public abstract class BaseVersionableEntity {

    @Id
    @Basic(optional = false)
    @NotNull
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", nullable = false)
    protected Long id;

    @Version
    @Column(name = "VERSION", nullable = false)
    protected Long version;

    @Basic
    @Column(name = "DELETED")
    protected boolean deleted;

    public boolean isDeleted() {
        return deleted;
    }

    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }

    // equals/hashCode

}

通过使用 hibernate 的 interceptor 机制,我配置了一个 软删除拦截器 以捕获 hibernate 执行的所有查询:

public class SoftSelectInterceptor extends EmptyInterceptor{

    private static final long serialVersionUID = 1L;

    @Override
    public String onPrepareStatement(String sql) {

        if(sql.startsWith("select")){
            sql = sql.replace("where ","where deleted=0 and ");
        }

        return super.onPrepareStatement(sql);
    }
}

我在 persistence.xml 文件中添加了以下拦截器配置

<property name="hibernate.ejb.interceptor" value="com.my.organization.SoftSelectInterceptor"/> 

请注意,还有一个 SoftDeleteInterceptor。以同样的方式工作。

当我执行此代码时,hibernate 会在 onPerparedStatement 方法中捕获 select 语句调用,但它会在表字段前添加表别名。也就是说如果表名是reportTemplate,hibernate给这个表reportTemplate0别名。所以我的替换语句需要如下所示:

sql = sql.replace("where ","where reportTemplate0.deleted=0 and ");

我可以解析 sql 字符串并尝试推断正确的表别名。但是在复杂的 sql 语句中(当同一个实体被多次使用时)很难推断。

我想知道是否有更好的方法来做到这一点。

【问题讨论】:

    标签: hibernate jpa


    【解决方案1】:

    我看到三个选项:

    1. 使用Hibernate Filters。主要缺点:您必须为每个实体附加过滤器。

      @Entity
      // The FilterDef can be declared at package level as well:
      @FilterDef(name="alive", defaultCondition="deleted = 0")
      // Turn on the filter for our entity:
      @Filter(name="alive")
      public class MyEntity { ... }
      
    2. 使用 SQL 解析器(已经足够了),修改解析结果并重新创建 SQL。主要缺点:有足够多的不那么琐碎的语句(包括子选择)需要修改。您必须确保仅将限制添加到受影响的表。

    3. 使用带有基础服务的服务架构,您始终使用相同的方法来加载/查找实体,从而构建正确的查询。主要缺点:您不能使用命名查询。

    【讨论】:

    • 选项 1 和 3 不适用于我们的案例,因为我应该提供一个影响最小的通用解决方案。我正在考虑选项 2。是否有任何全局配置参数来指定表别名的结构。比如使用t前缀(如t1、t2)还是使用alwayse实体类名前缀等?
    • 别名没有具体的配置参数。通常它是 Entity Name + a counter + _(参见 org.hibernate.hql.ast.util.AliasGenerator.createName(String)),但我不能保证所有生成的 SQL 都是如此。文档说 Loader 的子类 *not* 必须使用这种形式的别名
    • 我接受了您的回答,因为如果我们无视努力,选项 1 始终适用。最后一个问题:Hibernate 支持 MappedSupperClasses 中的过滤器注释(从 3.5.0 版开始)。如果我在映射的超类中只使用一个过滤器并强制项目中的所有实体都继承这个超类怎么办。这种方法有什么缺点吗?
    • 您应该检查过滤器是否也应用于实体集合。据我所知,可以为集合设置过滤器。而且我不知道您是否必须将其设置为强制执行过滤器。
    • 过滤器也需要在集合上声明。另外,请记住,过滤器对ToOne 关联(按 id 搜索)无效。如果不可删除实体在OneToOne 关联中与可删除实体相关联并且外键列不可为空,则这可​​能是一个问题。然后将其更改为可为空,并确保在软删除可删除实例时破坏关联(通过在关联的拥有方设置 null)。
    【解决方案2】:

    您可以将可删除实体映射到数据库视图,这些视图使用过滤条件 (deleted = 0) 从原始表中进行选择。

    编辑

    不幸的是,你不可能按照你想要的方式去做。

    1) 无法准确预测别名的外观,尤其是对于具有多个连接和子查询的复杂查询。即使您分析源代码,它也不是公共 API 的一部分,因此可能会发生变化。

    但是,如果您决定进行这项工作(分析 Hibernate 源以进行查询解析和 SQL 生成),请记住,您可能整个夏天都只做这件事,因为它是一个复杂而庞大的代码库和一个很少有非常重要的功能请求(如left joins for non-associated entities)等待多年才能重构。

    2) 没有where 子句的查询(和子查询)不会排除已删除的行(因为您的替换将找不到匹配项):

    select c from MyEntity;
    

    3) where 字母组合不太可能,但仍有可能,where 可能是标识符或文本常量的一部分,您的替换将使查询无效。假设有人向Trip 实体添加两个字段:fromWheretoWhere。也许不是一个好的命名示例,但你明白了。

    您可以通过仅替换整个 where 单词的出现来解决它,但然后想象如下查询:

    select ... from ... where aColumn = 'A constant with the where word in it'
    

    【讨论】:

    • 我也想过这个答案 - 但忽略了它,因为它取决于数据库是否可以通过视图更改实体。而且我不能为这些视图和(更重要的)表使用模式生成,至少我需要额外的转换。
    • 据我了解,没有痛苦的解决方案。感谢@Dragan 的编辑
    猜你喜欢
    • 1970-01-01
    • 2012-09-26
    • 2012-04-06
    • 1970-01-01
    • 2023-03-31
    • 2013-07-04
    • 1970-01-01
    • 2014-09-12
    • 1970-01-01
    相关资源
    最近更新 更多