【问题标题】:How to use MySQL's full text search from JPA如何使用来自 JPA 的 MySQL 全文搜索
【发布时间】:2012-02-08 01:59:20
【问题描述】:

我想通过 JPA 使用 MySQL's full text search 功能,而不必使用本机查询。

我正在使用 EclipseLink,它具有支持原生 SQL 命令的功能:FUNC。但是,帮助示例仅显示了它与简单的 MySQL 函数一起使用。我尽最大努力让它与 MATCH & AGAINST 一起工作如下:

@PersistenceContext(name="test")
EntityManager em;

Query query = em.createQuery("SELECT person FROM People person WHERE FUNC('MATCH', person.name) FUNC('AGAINST', :searchTerm)");
...
query.getResultList();

这给出了以下异常:

Caused by: NoViableAltException(32@[()* loopback of 822:9: (m= MULTIPLY right= arithmeticFactor | d= DIVIDE right= arithmeticFactor )*])
    at org.eclipse.persistence.internal.libraries.antlr.runtime.DFA.noViableAlt(DFA.java:159)
    at org.eclipse.persistence.internal.libraries.antlr.runtime.DFA.predict(DFA.java:116)
    at org.eclipse.persistence.internal.jpa.parsing.jpql.antlr.JPQLParser.arithmeticTerm(JPQLParser.java:4557)
    ... 120 more

我对使用 FUNC 方法的其他替代方案持开放态度。

我正在使用 EJB 3 和 EclipseLink 2.3.1。

【问题讨论】:

  • 你尝试过 createNativeQuery 吗?

标签: jpa full-text-search eclipselink


【解决方案1】:

@Markus Barthlen 的改进答案,适用于 Hibernate。

创建自定义方言

public class MySQLDialectCustom extends MySQL5Dialect {
  public MySQLDialect() {
    super();
    registerFunction("match", new SQLFunctionTemplate(StandardBasicTypes.DOUBLE,
        "match(?1) against  (?2 in boolean mode)"));
  }
}

并通过设置hibernate.dialect属性注册它。

使用它

在 JPQL 中:

Query query = entityManager
    .createQuery("select an from Animal an " +
             "where an.type = :animalTypeNo " +
             "and match(an.name, :animalName) > 0", Animal.class)
    .setParameter("animalType", "Mammal")
    .setParameter("animalName", "Tiger");
List<Animal> result = query.getResultList();
return result;

或使用 Criteria API:

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Animal> criteriaQuery = criteriaBuilder.createQuery(Animal.class);
Root<Animal> root = criteriaQuery.from(Animal.class);

List<Predicate> predicates = new ArrayList<>();

Expression<Double> match = criteriaBuilder.function("match", Double.class, root.get("name"),
criteriaBuilder.parameter(String.class, "animalName"));

predicates.add(criteriaBuilder.equal(root.get("animalType"), "Mammal"));
predicates.add(criteriaBuilder.greaterThan(match, 0.));

criteriaQuery.where(predicates.toArray(new Predicate[]{}));

TypedQuery<Animal> query = entityManager.createQuery(criteriaQuery);
List<Animal> result = query.setParameter("animalName", "Tiger").getResultList();

return result;

此博文中的更多详细信息:http://pavelmakhov.com/2016/09/jpa-custom-function

【讨论】:

  • 我想知道在 2019 年是否仍然没有简洁且非侵入性的解决方案,就像我们使用旧 API 一样:ftsCriteria.add(Restrictions.sqlRestriction("MATCH ( {alias}.fts ) AGAINST (?)", text, StringType.INSTANCE))
【解决方案2】:

FUNC 仅适用于普通打印函数,

即 匹配(arg1,arg2)

由于 MATCH arg1 AGAINST arg2 的打印方式与通常打印函数的方式不同,因此无法使用 FUNC 调用它。

EclipseLink ExpressionOperators 确实支持这样的打印功能,因此您可以定义自己的 ExpressionOperator,但目前仅通过 EclipseLink Expression 查询支持 ExpressionOperators,而不是通过 JPQL。您可以记录增强功能以​​在 JPQL 中获得操作员支持。

您也可以使用原生 SQL 查询。

【讨论】:

  • 你能给出这个答案的任何例子或者可以详细说明一下
【解决方案3】:

只是为了完成答案:我遇到了同样的问题,但使用了标准生成器。如果您使用的是 EclipseLink,这就是您可以绕过标准实现中的限制的方法:

  1. 将 JPA 表达式转换为 EclipseLink 表达式
  2. 使用sql方法
  3. 如果匹配复合索引,请使用函数方法创建它

示例:

    JpaCriteriaBuilder cb = (JpaCriteriaBuilder) cb;

    List<String> args = new ArrayList();
    args.add("Keyword");

    Expression<Boolean> expr = cb.fromExpression (
      cb.toExpression(
         cb.function("", String.class,
           table.get(Table_.text1), table.get(Table_.text2)) 
      )                          
       .sql("MATCH ? AGAINST (?)", args)
     );

    query.where(expr);

如果您需要将表达式转换为谓词,请使用以下命令:

query.where( cb.gt(expr, 0));

【讨论】:

    【解决方案4】:

    EclipseLink 4.0 中的新 SQL 运算符怎么样?我认为它可以帮助您从 JPQL 进行全文搜索。但是你必须升级到 EclipseLink 4.0。

    http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Support_for_Native_Database_Functions#SQL

    编辑:
    抱歉更新晚了。
    验证正确使用 EclispeLink 2.4.0 "SQL" 运算符和 MySQL 全文搜索是

    SELECT person FROM People person WHERE SQL('MATCH(name) AGAINST( ? )', :searchTerm)"
    

    其中name 是定义全文索引的列。 :searchTerm 是您用于搜索的字符串。
    工作没有问题。

    【讨论】:

    • 在这种情况下如何使用'new'操作符?我看不出来
    • 我将在 2-3 天内亲自尝试“SQL”运算符的这个特殊用例。如果我能成功,我将用精确的解决方案编辑我的答案。首先,我知道它应该使用类似“FUNC”运算符:“SELECT person FROM People person WHERE SQL('MATCH(name) AGAINST(? )', :searchTerm)”
    【解决方案5】:

    详细说明詹姆斯的回答:

    似乎我很幸运地使用了 mysql 方言

    registerFunction("match", new SQLFunctionTemplate(DoubleType.INSTANCE,     "match(?1) against  (?2 in boolean mode)")); 
    

    并通过以下 jpql 片段调用函数

    match(" + binaryDataColumn + ",'" + StringUtils.join(words, " ") + "') > 0 
    

    我不得不猜测返回类型,但这应该可以帮助您入门。

    【讨论】:

      【解决方案6】:

      终于成功了

      如果您将表列设置为索引完整搜索

      @NamedNativeQuery(name = "searchclient", query = "SELECT * FROM client WHERE MATCH(clientFullName, lastname, secondname, firstphone," + " secondphone, workphone, otherphone, otherphone1," + " otherphone2, detailsFromClient, email, company," + "地址,contractType,paymantCondition) AGAINST(?)",

      List list = em.createNamedQuery("searchclient").setParameter(1, searchKey).getResultList();

      【讨论】:

        【解决方案7】:

        最简单的变体是使用 NativeQuery

        使用它映射到 JPA 实体 (FiasAddress) 的示例:

        public class FiasServiceBean implements FiasService {
        
            @PersistenceContext(unitName = "fias")
            EntityManager entityManager;
        
            @Override
            public Collection<FiasAddress> search(String name, int limit, int aolevel) {
                Query query = entityManager.createNativeQuery(
                        "SELECT fa.* FROM fias.addressobject fa" +
                                " WHERE MATCH(FORMALNAME) AGAINST (:name IN NATURAL LANGUAGE MODE)" +
                                " AND AOLEVEL = :AOLEVEL" +
                                " LIMIT :limit",
                        FiasAddress.class
                );
                query.setParameter("name", name);
                query.setParameter("limit", limit);
                query.setParameter("AOLEVEL", aolevel);
                Iterator iterator = query.getResultList().iterator();
                ArrayList<FiasAddress> result = new ArrayList<>();
                while (iterator.hasNext()) {
                    result.add((FiasAddress) iterator.next());
                }
                return result;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2018-06-18
          • 1970-01-01
          • 2020-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-22
          • 2015-07-03
          相关资源
          最近更新 更多