在Hibernate Interceptor中正确获取表别名

时间:2015-06-30 07:14:19

标签: hibernate jpa

我需要为我的应用程序管理的所有实体添加软删除功能。为了实现这一点,我创建了一个带有已删除字段的基本实体类(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的拦截器机制,我配置了一个软删除拦截器,以便捕获所有hi​​bernate执行的查询:

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会在 onPeraparedStatement 方法中捕获select语句调用,但它会在表字段前面添加一个表别名。也就是说,如果表名是 reportTemplate ,则hibernate会为此表提供 reportTemplate0 别名。所以我的替换语句需要如下所示:

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

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

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

2 个答案:

答案 0 :(得分: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 :(得分:0)

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

修改

不幸的是,按照你想要的方式做到这一点是不可能的。

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

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

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

select c from MyEntity;

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

你可以通过仅替换整个where单词的出现来解决它,但是想象一下这样的查询:

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