JPA中的PostgreSQL函数string_agg

11
在PostgreSQL中,string_agg(column, separator)允许聚合一些字符串。我尝试在JPA中使用它,但它不是JPA的标准函数。
注意:这不等同于CriteriaBuilder#concat()
因此,我尝试告诉JPA这个函数存在,像这样:
public class StringAgg extends ParameterizedFunctionExpression<String> implements Serializable {

  public static final String NAME = "string_agg";

  @Override
  public boolean isAggregation() {
    return true;
  }

  @Override
  protected boolean isStandardJpaFunction() {
    return false;
  }

  public StringAgg(CriteriaBuilderImpl criteriaBuilder, Expression<String> expression, String separator) {
    super(criteriaBuilder, String.class, NAME, expression, new LiteralExpression(criteriaBuilder, separator));
  }
}

那么:
Expression<String> exprStr = ...
CriteriaBuilder cb = ...
cb.construct(MyClass.class, 
             myClass.get(MyClass_.name),
             myClass.get(MyClass_.surname),
             new StringAgg(cb, exprStr, "/"));

问题,我遇到了一个空指针异常!
java.lang.NullPointerException: null
at org.hibernate.internal.util.ReflectHelper.getConstructor(ReflectHelper.java:355) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.ast.tree.ConstructorNode.resolveConstructor(ConstructorNode.java:179) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.ast.tree.ConstructorNode.prepare(ConstructorNode.java:152) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.ast.HqlSqlWalker.processConstructor(HqlSqlWalker.java:1028) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectExpr(HqlSqlBaseWalker.java:2279) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectExprList(HqlSqlBaseWalker.java:2145) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectClause(HqlSqlBaseWalker.java:1451) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:571) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:299) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:247) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:261) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:189) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:141) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:119) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:87) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:190) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.internal.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:288) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]
at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:223) ~[hibernate-core-4.3.0.Beta3.jar:4.3.0.Beta3]

调试器显示,cb.construct() 的最后一个 Selection (new StringAgg(cb, exprStr, "/")) 被忽略了。因此,搜索到的构造函数是 MyClass(String,String) 而不是 MyClass(String, String, String)
StringAgg 的实现有问题吗?有人已经尝试在 JPA 中使用 string_agg 吗?
解决方案(感谢 vzamanillo):
扩展方言:
public class PGDialect extends PostgreSQLDialect{

  public PGDialect() {
    super();
    registerFunction("string_agg", new SQLFunctionTemplate( StandardBasicTypes.STRING, "string_agg(?1, ?2)"));
  }
}

persistence.xml 中使用它。

<properties>
  <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
  <property name="hibernate.dialect" value="path.to.PGDialect"/>

然后使用 CriteriaBuilder#function() 方法:

Expression<String> exprStr = ...
CriteriaBuilder cb = ...
cb.construct(MyClass.class, 
             myClass.get(MyClass_.name),
             myClass.get(MyClass_.surname),
             cb.function( "string_agg", myColPath, cb.literal("delimiter" )));

为了简化操作,我创建了一个辅助方法:
public static Expression<String> strAgg(CriteriaBuilder cb, Expression<String> expression, String delimiter) {
  return cb.function( "string_agg", String.class, expression, cb.literal(delimiter));
}

因此代码变为:
Expression<String> exprStr = ...
CriteriaBuilder cb = ...
cb.construct(MyClass.class, 
             myClass.get(MyClass_.name),
             myClass.get(MyClass_.surname),
             strAgg(cb, myColPath, "delimiter"));

FUNCTION是JPA(2.1)标准函数。也许可以使用它。 - DataNucleus
1
为什么不使用类的普通构造函数,相当于在JPQL中的 SELECT new com.me.Entity(path1, path2)...?(附言:我学到了新东西,点赞+1) - V G
3个回答

13

或许这可以帮到你,

在 JPA Criteria Query 中,你可以调用数据库函数。

CriteriaBuilder 接口有一个名为 "function" 的方法。

<T> Expression<T> function(String name,
                         Class<T> type,
                         Expression<?>... args)

Create an expression for the execution of a database function.

Parameters:
    name - function name
    type - expected result type
    args - function arguments
Returns:
    expression

那么你可以尝试创建一个CriteriaBuilder辅助类,以获取一个普通的Criteria表达式,您可以像通常在我们的Criteria查询中一样使用它。

public abstract class CriteriaBuilderHelper {

    private static final String PG_STRING_AGG  = "string_agg";

    /**
    * @param cb the CriteriaBuilder to use
    * @param toJoin the string to join
    * @param delimiter the string to use
    * @return Expression<String>
    */
    public static Expression functionStringAgg(CriteriaBuilder cb, String toJoin, String delimiter) {
        return cb.function(PG_STRING_AGG, 
            String.class,
            cb.literal(toJoin),
            cb.literal(delimiter))
        );
    }
}

或者您可以使用自定义方言来注册新函数

public class PGDialect extends PostgreSQLDialect{

    public PGDialect() {
        super();
        registerFunction("string_agg", new SQLFunctionTemplate( StandardBasicTypes.STRING, "string_agg(?1, ?2)"));
    }
}

并将其像普通函数一样在您的CriteriaBuilder中使用

Expression<String> functionStringAgg = cb.function( "string_agg", String.class, 
                                cb.parameter(String.class, "toJoin" ), 
                                cb.parameter(String.class, "delimiter"));

在所有操作完成后,别忘了将参数值设置到你的 CriteriaQuery 中。

setParameter( "toJoin", toJoin);
setParameter( "delimiter", delimiter);

你太棒了 :) 第一个解决方案没有起作用:我最终还是遇到了同样的问题。我的代码几乎一样。但是通过新的方言注册函数却非常成功。 - Arnaud Denoyelle
@Arnaud Denoyelle 你能帮忙解决 https://stackoverflow.com/questions/55218784/string-agg-with-criteria-builder 这个问题吗? - bharathi
@vzamanillo,你能帮忙解决stackoverflow.com/questions/55218784/…这个问题吗? - bharathi
我得到了这个错误java.lang.IllegalStateException: 节点没有数据类型:org.hibernate.hql.internal.ast.tree.MethodNode \-[METHOD_CALL] MethodNode: 'function (string_agg)' +-[METHOD_NAME] IdentNode: 'string_agg' {originalText=string_agg} \-[EXPR_LIST] SqlNode: 'exprList' +-[NAMED_PARAM] ParameterNode: '?' {name=empId, expectedType=null} \-[NAMED_PARAM] ParameterNode: '?' {name=delimiter, expectedType=null} - rushabh sojitra
现在是2023年,除了使用特定的方言,还没有现代化的解决方案。 - undefined

2
我使用相同的结构,但是简化方法来完成了它:
修改了持久性文件:
    <property name="jpaProperties">
        <props>
                <prop key="hibernate.dialect">es.gmrcanarias.saga.utiles.PGDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
...
        </props>
    </property>

然后注册该函数:
import org.hibernate.dialect.PostgreSQL82Dialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.type.StandardBasicTypes;

public class PGDialect extends PostgreSQL82Dialect {

   public PGDialect() {
       super();
      registerFunction("string_agg", new  
       SQLFunctionTemplate(StandardBasicTypes.STRING, "string_agg(?1, ?2)"));
   }
}

然后添加了查询:
    Join<Razon, Incidenc> subquery;
    ....
    Expression<String> functionStringAgg = criteriaBuilder.function("string_agg", 
        String.class,
        subquery.get(CODIGO), 
        criteriaBuilder.literal(", "));
...
    subqueryList.select(functionStringAgg);    

1
一个关于PGDialect的细节: 你可以添加第三个参数以使用ORDER BY。
public class PGDialect extends PostgreSQL9Dialect {


     public PGDialect() {
         super();
         this.registerFunction("string_agg", new SQLFunctionTemplate( StandardBasicTypes.STRING, "string_agg(?1, ?2)") );
         this.registerFunction("string_agg", new SQLFunctionTemplate( StandardBasicTypes.STRING, "string_agg(?1, ?2 ORDER BY ?3 )") );
     }
 }

HQL查询用法

 "SELECT string_agg(f.name, '; ', f.name) FROM Foo as f "

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接