1

そのため、ファイルに保存され、PHP から使用される多数の Postgres SQL クエリがあります。タスクは、PHP を Java に置き換えることです。移行パスを短く保つために、クエリを「そのまま」再利用したいと考えています。配列パラメーターを機能させることができません。

クエリの例を次に示します。

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)

Wireshark を使用して、両方の API がデータベースと通信しているときにこれを見つけました。

画像: Wireshark を使用したデータベース呼び出しの比較

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

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

JPA EntityManager のバックエンドとして Hibernate を使用して、ネイティブ クエリで配列パラメータを使用できた人はいますか? もしそうなら:どのように?

4

2 に答える 2

1

Hibernate セッションを EntityManager からアンラップし、JDBC PreparedStatement を使用することで、この問題を回避することができました。JDBC PreparedStatement は、何の不満もなく java.sql.Array パラメータを消費します。

以下の例で使用されているNamedParameterStatementについては、こちらで説明しています(必要に応じて変更しました)。これは 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();
}
于 2016-04-20T13:02:45.347 に答える