【问题标题】:How to use the Postgres any-clause with JPA/Hibernate native queries (array parameters)如何将 Postgres 任意子句与 JPA/Hibernate 本机查询(数组参数)一起使用
【发布时间】:2016-08-04 17:18:50
【问题描述】:

因此,我们已经将大量 Postgres SQL 查询存储在文件中并从 PHP 中使用。任务是用 Java 替换 PHP。我们希望“按原样”重用尽可能多的查询,以保持迁移路径短。我无法让 Array 参数工作。

这是一个查询示例:

update user_devices
set some_date = now()
where some_id in (
    select distinct some_id from user_devices 
    where user_id = any(:userIDs) and device_id = any(:deviceIDs)
    and exists (select 1 from users where user_id = any(:userIDs) and customer_id = :customerID)
);

注意导致问题的“any”子句,因为它们需要一个数组类型。 这就是我们从 PHP 中使用它们的方式:

$this->allValues['userIDs'] = '{' . implode ( ",", $userIdNodes ) . '}';
$this->allValues['deviceIDs'] = '{' . implode ( ",", $deviceIdNodes ) . '}';
$this->allValues['customerID'] = customerID;
$this->db->runQuery ( $this->getQuery ( 'my_query' ), $this->allValues );

所以作为参数,数组类型看起来像“{111,222}”。

这是我在 Java 中尝试过的:

    Integer customerID = 1;
    int[] userIDs  = new int[]{111,222};
    int[] deviceIDs= new int[]{333,444};
    //List<Integer> userIDs  = Arrays.asList(111,222);
    //List<Integer> deviceIDs= Arrays.asList(333,444);
    //java.sql.Array userIDs  = toArray("integer", new int[]{111,222}));
    //java.sql.Array deviceIDs= toArray("integer", new int[]{333,444}));
    //java.sql.Array userIDs  = toArray("integer", Arrays.asList(111,222)));
    //java.sql.Array deviceIDs= toArray("integer", Arrays.asList(333,444)));
    //String userIDs  = "{111,222}";
    //String deviceIDs= "{333,444}";
    //String userIDs  = "ARRAY[111,222]";
    //String deviceIDs= "ARRAY[333,444]";

    Query nativeQuery = em.createNativeQuery(queryString);
    nativeQuery.setParameter("userIDs", userIDs);
    nativeQuery.setParameter("deviceIDs", deviceIDs);
    nativeQuery.setParameter("customerID", customerID);
    //nativeQuery.setParameter(createParameter("userIDs",java.sql.Array.class), userIDs);
    //nativeQuery.setParameter(createParameter("userIDs",java.sql.Array.class), deviceIDs);
    //nativeQuery.setParameter(createParameter("customerID", Integer.class), customerID);
    query.executeUpdate();

//[...]
private Array toArray(String typeName, Object... elements) {
    Session session = em.unwrap(Session.class); // ATTENTION! This is Hibernate-specific!
    final AtomicReference<Array> aRef = new AtomicReference<>();
    session.doWork((c) -> {
        aRef.set(c.createArrayOf(typeName, elements));
    });
    return aRef.get();
}

private <T> Parameter<T> createParameter(final String name, final Class<?> clazz) {
    return new Parameter<T>() {
        @Override
        public String getName() {
            return name;
        }
        @Override
        public Integer getPosition() {
            return null; // not used
        }
        @Override
        public Class<T> getParameterType() {
            return (Class<T>) clazz;
        }
    };
}

这些都不起作用我会遇到以下异常之一: 使用“toArray”方法时:

Caused by: org.hibernate.HibernateException: Could not determine a type for class: org.postgresql.jdbc4.Jdbc4Array
    at org.hibernate.internal.AbstractQueryImpl.guessType(AbstractQueryImpl.java:550)
    at org.hibernate.internal.AbstractQueryImpl.guessType(AbstractQueryImpl.java:534)
    at org.hibernate.internal.AbstractQueryImpl.determineType(AbstractQueryImpl.java:519)
    at org.hibernate.internal.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:487)
    at org.hibernate.jpa.internal.QueryImpl$ParameterRegistrationImpl.bindValue(QueryImpl.java:247)
    at org.hibernate.

或者当使用 int[] 或 Strings 时,我会得到:

Caused by: org.postgresql.util.PSQLException: ERROR: op ANY/ALL (array) requires array on right side
  Position: 137
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2270)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1998)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:570)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:420)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:366)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.postgresql.ds.jdbc23.AbstractJdbc23PooledConnection$StatementHandler.invoke(AbstractJdbc23PooledConnection.java:453)
    at com.sun.proxy.$Proxy274.executeUpdate(Unknown Source)
    at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:125)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
    jpa.spi.BaseQueryImpl.setParameter(BaseQueryImpl.java:582)

当两个 API 都与数据库通信时,我使用 Wireshark 发现了这一点:

Image: Comparison of database calls with Wireshark

select oid, typname from pg_type where oid in (0, 23, 1043) order by oid;

oid   |typname
------+-------
23    |int4
1043  |varchar

有没有人设法将数组参数与使用 Hibernate 作为 JPA EntityManager 的后端的本机查询一起使用?如果是:如何?

【问题讨论】:

    标签: postgresql jpa parameter-passing nativequery


    【解决方案1】:

    将您的查询从where user_id = any(:userIDs) 更改为where user_id IN (:userIDs),并将userIDs 数组更改为一个集合,例如List&lt;Long&gt;。您必须额外保护它为空列表,但它会起作用。

    【讨论】:

      【解决方案2】:

      我能够通过从 EntityManager 中解开 Hibernate 会话并使用 JDBC PreparedStatement 来解决这个问题,它会毫无怨言地吃掉 java.sql.Array 参数。

      下面示例中使用的NamedParameterStatement 描述为here(我已根据需要对其进行了修改)。它委托给 PreparedStatement。

      其余代码大致如下:

      public int executeUpdate(...){
          //....
          Integer customerID = 1;
          java.sql.Array userIDs  = toArray("integer", new int[]{111,222}));
          java.sql.Array deviceIDs= toArray("integer", new int[]{333,444}));
      
          final AtomicInteger rowsModifiedRef = new AtomicInteger();
          final Session session = em.unwrap(Session.class); // ATTENTION! This is Hibernate-specific!
          session.doWork((c) -> {
              try (final NamedParameterStatement statement = new NamedParameterStatement(c, queryString)) {
                  statement.setObject("deviceIDs", userIDs);
                  statement.setObject("userIDs", userIDs);
                  statement.setObject("customerID", userIDs);
                  rowsModifiedRef.set(statement.executeUpdate());
              }
          });
          return rowsModifiedRef.get();
      }
      
      private Array toArray(String typeName, Object... elements) {
          Session session = em.unwrap(Session.class); // ATTENTION! This is Hibernate-specific!
          final AtomicReference<Array> aRef = new AtomicReference<>();
          session.doWork((c) -> {
              aRef.set(c.createArrayOf(typeName, elements));
          });
          return aRef.get();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-09
        • 2017-04-13
        • 1970-01-01
        • 1970-01-01
        • 2012-12-17
        • 2021-09-08
        • 2019-06-21
        • 2011-11-08
        相关资源
        最近更新 更多