【问题标题】:PostgreSQL function string_agg in JPAJPA 中的 PostgreSQL 函数 string_agg
【发布时间】:2013-12-24 00:07:59
【问题描述】:

在 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, "/"));

问题,我得到一个 NullPointerException

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() (new StringAgg(cb, exprStr, "/")) 中的最后一个Selection 被忽略。因此,搜索到的构造函数是 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) 标准函数。也许使用那个
  • 为什么不使用类的普通构造函数,相当于JPQL 中的SELECT new com.me.Entity(path1, path2)...? (PS:+1 因为我学到了一些新东西)

标签: java postgresql jpa


【解决方案1】:

也许这对你有帮助,

您可以在 JPA 标准查询中调用数据库函数。

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 帮助类来获取一个普通的条件表达式,你可以像往常一样在我们的条件查询中使用它

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 你能帮忙解决stackoverflow.com/questions/55218784/… 问题吗
  • @vzamanillo 你能帮忙解决stackoverflow.com/questions/55218784/...问题吗
  • 我收到了这个错误`java.lang.IllegalStateException: No data type for node: 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} `
【解决方案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);    

【讨论】:

    【解决方案3】:

    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 "
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-25
      • 2017-08-01
      • 1970-01-01
      • 2020-07-29
      • 1970-01-01
      • 2016-10-12
      • 2021-06-07
      相关资源
      最近更新 更多