【问题标题】:how to use Oracle's regexp_like in Hibernate HQL?如何在 Hibernate HQL 中使用 Oracle 的 regexp_like?
【发布时间】:2012-08-01 08:25:19
【问题描述】:

我正在使用oracle 10ghibernate 3.3.2。之前在sql中使用过正则表达式,现在第一次在HQL中使用。

Query query = getSession().createQuery("From Company company 
where company.id!=:companyId and 
regexp_like(upper(rtrim(ltrim(company.num))), '^0*514619915$' )");

这是我的 hql,当我在没有 regex_like 函数的情况下运行它时,它会按预期运行。但我无法用regex_like 表达式执行它。

它说..

嵌套异常是 org.hibernate.hql.ast.QuerySyntaxException: 意外的 AST 节点:(靠近第 1 行,第 66 列.....

请帮忙,我如何在休眠原生查询中使用regex_like?或其他替代方法。

【问题讨论】:

  • 为什么要强制它返回布尔值以外的任何值? Oracle SQL 只是愚蠢地设计为没有布尔数据类型,但您不需要这里的 0 和 1 或“Y”和“N”结果。

标签: java sql oracle hibernate hql


【解决方案1】:

实际上,除了 PL/SQL 中的条件语句之外,您无法将 REGEXP_LIKE 的结果与任何东西进行比较。

Hibernate 似乎不接受没有 returnType 的自定义函数,因为您总是需要将输出与某些东西进行比较,即:

REGEXP_LIKE('bananas', 'a', 'i') = 1

由于 Oracle 不允许您将此函数的结果与空值进行比较,我想出了一个使用 case 条件的解决方案:

public class Oracle10gExtendedDialect extends Oracle10gDialect {

    public Oracle10gExtendedDialect() {
        super();
        registerFunction(
          "regexp_like", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN,
          "(case when (regexp_like(?1, ?2, ?3)) then 1 else 0 end)")
        );
    }

}

您的 HQL 应该如下所示:

REGEXP_LIKE('bananas', 'a', 'i') = 1

它会起作用的:)

【讨论】:

  • 如果您只需要按原样返回布尔值,为什么还要返回 1 和 0?
【解决方案2】:

您绝对可以通过 Hibernate HQL(和 JPQL,只要 Hibernate 是提供者)使用您希望使用的任何类型的数据库特定功能。你只需要告诉 Hibernate 这些函数。在 3.3 中,唯一的选择是提供自定义方言并从方言的构造函数中注册函数。如果你看一下基本的 Dialect 类,你会看到很多注册函数的例子。通常最好扩展您当前使用的确切方言,并简单地提供您的扩展(这里,注册函数)。

有趣的一点是,Oracle 没有将 regexp_like 归类为函数。他们将其归类为条件/谓词。我认为这主要是因为 Oracle SQL 没有定义 BOOLEAN 数据类型,即使他们的 PL/SQL 定义了,我敢打赌 regexp_like 被定义为返回 BOOLEAN 的 PL/SQL 函数......

假设您当前使用 Oracle10gDialect,您会这样做:

public class MyOracle10gDialect extends Oracle10gDialect {
    public Oracle10gDialect() {
        super();

        registerFunction( 
            "regexp_like", 
             new StandardSQLFunction( "regexp_like", StandardBasicTypes.BOOLEAN )
        );
    }
}

我不记得 HQL 解析器是否喜欢返回布尔值的函数,但是就其本身作为谓词而言。您可能不得不将 true/false 转换为其他内容并检查该返回:

public class MyOracle10gDialect extends Oracle10gDialect {
    public Oracle10gDialect() {
        super();

        registerFunction( 
            "regexp_like", 
             new StandardSQLFunction( "regexp_like", StandardBasicTypes.INTEGER ) {
                 @Override
                 public String render(
                         Type firstArgumentType, 
                         List arguments, 
                         SessionFactoryImplementor factory) {
                     return "some_conversion_from_boolean_to_int(" + 
                             super.render( firstArgumentType, arguments, factory ) +
                             ")";
                 }
             }
        );
    }
}

【讨论】:

  • 谢谢史蒂夫,你能给我提供一些关于如何在休眠中抵抗该功能的示例代码或参考网址吗?
【解决方案3】:

您无法访问特定的数据库函数,除非 JPAQL/HQL 提供了这样做的方法,并且也没有为正则表达式提供任何东西。所以你需要编写一个原生 SQL 查询来使用正则表达式。

另一方面,非常重要的一点是,一些同事(Oracle DBA)告诉我不要在 oracle 中使用正则表达式,因为它们无法被索引,最终会在数据库中执行完整的数据库扫描。如果表有几个条目,那没关系,但如果它有很多行,它可能会削弱性能。

【讨论】:

  • 您仍然可以使用索引,但您需要使用 Oracle 的基于函数的索引。 DBA 建议您“不要使用正则表达式”?我会礼貌地请他们对此进行一些研究,因为您会错过一些非常强大的功能。
  • 他们说从不在数据库的上下文中,而不是在其他情况下。你可能是对的,我听说他们谈论开始使用基于函数的索引作为下一个很酷的事情:)。所以建议是:只有在表很小或列上有函数索引时才在表上使用正则表达式。感谢奥利的评论!
  • 我的做法是使用正则表达式,它可以准确地为我提供我想要的数据,但添加最接近的 LIKE 表达式以利用索引。
  • 如果你控制了正则表达式,你可以为它创建一个函数索引,oracle会使用它。但是,如果您有一个动态的正则表达式可能会有所不同,则无法为其创建函数索引。
【解决方案4】:

您可以尝试使用标准的 LIKE 运算符:

where company.num like '%514619915'

然后使用 Java 正则表达式过滤掉不需要的。这应该会减少将返回的不需要的行数。

这不会使用索引,因为它以“%”开头。

【讨论】:

    【解决方案5】:

    对于那些使用带有 sqlRestriction 的 Hibernate 标准(Hibernate 版本 4.2.7)

     Criterion someCriterion = Restrictions.sqlRestriction("regexp_like (column_name, ?, 'i')", "(^|\\s)"+searchValue+"($|\\s|.$)", StringType.INSTANCE);
    

    【讨论】:

      【解决方案6】:

      或者另一种选择是在 oracle 中创建类似的函数,该函数将根据运算结果返回数值。类似的东西

      CREATE OR REPLACE FUNCTION MY_REGEXP_LIKE(text VARCHAR2, pattern VARCHAR2)
      RETURN NUMBER 
      IS function_result NUMBER;
      BEGIN
        function_result := CASE WHEN REGEXP_LIKE(text, pattern) 
        THEN 1 
        ELSE 0
        END;    
        RETURN(function_result);
      END MY_REGEXP_LIKE;
      

      你可以使用

      MY_REGEXP_LIKE('bananas', 'a') = 1
      

      【讨论】:

        【解决方案7】:

        您可以使用规范。

         Specification<YourEntity> specification = (root, query, builder) -> builder.equal(builder.selectCase()
                    .when(builder.function("regexp_like", Boolean.class, root.get("your_field"), builder.literal("^0*514619915$")), 1)
                    .otherwise(0), 1);
            List<YourEntity> yourEntities = yourEntityRepository.findAll(specification);
        

        【讨论】:

        • 这看起来非常好,但我只是尝试了一下,它不起作用。似乎hibernate坚持具有必须等于某些东西的功能。我仍然收到关于 AST 节点的异常
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-06
        • 2015-02-15
        • 2016-08-06
        • 2021-11-19
        • 2018-05-30
        • 1970-01-01
        相关资源
        最近更新 更多