14

I'm stuck on a specific issue using array type in postgresql 9.3 mapped with hibernate 4.1.0. This type allows me to have really strong data model, without building lots of tables and joins.

In order to map a field stored with this particular type, I have used a UserType

Anyway, it works well with pure hibernate (hql) but I need also to send sql native query to my database. When I do it, in spite of many tries, I have not found any way to do that.

I try many syntaxes based on this

String[] values = {"value1", "value2"};
String queryString = "SELECT * FROM instances WHERE values && :values";
Query query = this.getSession().createSQLQuery(queryString).addEntity(Instance.class);
query.setParameterList("values", values);
query.list();

I got Operator does not exists : text[] && character varying

It should give following syntax in jdbc : ['value1', 'value2'] and it seems to give 'value1'...

I tried many syntaxes with

  • Collection
  • Pure Arrays
  • [ :values ] syntax : I got Syntax error near "["

I need to send native query because I use Materialized View for performance gains.

My SQL Query works in postgresql console. So it is an hibernate specific issue.

Martin Schröder
  • 4,176
  • 7
  • 47
  • 81
Damien C
  • 969
  • 1
  • 10
  • 16
  • 1
    possible duplicate of [mapping a postgres array with hibernate](http://stackoverflow.com/questions/1647583/mapping-a-postgres-array-with-hibernate) – borchvm Oct 07 '14 at 08:54

2 Answers2

29

I tried few versions based on Array Type introduced by JDBC4 : How can I set a String[] parameter to a native query?. Problem is also Hibernate (even in last version 4.3.1.final) does not work with this new features and gave me following error message

Could not determine a type for class: org.postgresql.jdbc4.Jdbc4Array

So I had to make a Specific UserType (based on several articles in stackoverflow, and others sources)

My Model

@Type(type = "fr.mycompany.dao.hibernate.types.ArrayUserType")
private String[] values;

My ArrayUserType

public class ArrayUserType implements UserType {

/** Constante contenant le type SQL "Array".
 */
protected static final int[] SQL_TYPES = { Types.ARRAY };

/**
 * Return the SQL type codes for the columns mapped by this type. The
 * codes are defined on <tt>java.sql.Types</tt>.
 * 
 * @return int[] the typecodes
 * @see java.sql.Types
 */
public final int[] sqlTypes() {
    return SQL_TYPES;
}

/**
 * The class returned by <tt>nullSafeGet()</tt>.
 * 
 * @return Class
 */
public final Class returnedClass() {
    return String[].class;
}

/**
 * Retrieve an instance of the mapped class from a JDBC resultset. Implementors
 * should handle possibility of null values.
 * 
 * @param resultSet a JDBC result set.
 * @param names the column names.
 * @param session SQL en cours.
 * @param owner the containing entity 
 * @return Object
 * @throws org.hibernate.HibernateException exception levée par Hibernate
 * lors de la récupération des données.
 * @throws java.sql.SQLException exception SQL 
 * levées lors de la récupération des données.
 */
@Override
public final Object nullSafeGet(
        final ResultSet resultSet, 
        final String[] names, 
        final SessionImplementor session, 
        final Object owner) throws HibernateException, SQLException {
    if (resultSet.wasNull()) {
        return null;
    }

    String[] array = (String[]) resultSet.getArray(names[0]).getArray();
    return array;
}

/**
 * Write an instance of the mapped class to a prepared statement. Implementors
 * should handle possibility of null values. A multi-column type should be written
 * to parameters starting from <tt>index</tt>.
 * 
 * @param statement a JDBC prepared statement.
 * @param value the object to write
 * @param index statement parameter index
 * @param session sql en cours
 * @throws org.hibernate.HibernateException exception levée par Hibernate
 * lors de la récupération des données.
 * @throws java.sql.SQLException exception SQL 
 * levées lors de la récupération des données.
 */
@Override
public final void nullSafeSet(final PreparedStatement statement, final Object value, 
        final int index, final SessionImplementor session) throws HibernateException, SQLException {

    if (value == null) {
        statement.setNull(index, SQL_TYPES[0]);
    } else {
        String[] castObject = (String[]) value;
        Array array = session.connection().createArrayOf("text", castObject);
        statement.setArray(index, array);
    }
}

@Override
public final Object deepCopy(final Object value) throws HibernateException {
    return value;
}

@Override
public final boolean isMutable() {
    return false;
}

@Override
public final Object assemble(final Serializable arg0, final Object arg1)
        throws HibernateException {
    // TODO Auto-generated method stub
    return null;
}

@Override
public final Serializable disassemble(final Object arg0) throws HibernateException {
    // TODO Auto-generated method stub
    return null;
}

@Override
public final boolean equals(final Object x, final Object y) throws HibernateException {
    if (x == y) {
        return true;
    } else if (x == null || y == null) {
        return false;
    } else {
        return x.equals(y);
    }
}

@Override
public final int hashCode(final Object x) throws HibernateException {
    return x.hashCode();
}

@Override
public final Object replace(
    final Object original,
    final Object target,
    final Object owner) throws HibernateException {
    return original;
}

}

And the last, but least (that's what I missed) : when I need to run SQL Native Query, I have to force the parameter type with the following syntax

String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
Martin Schröder
  • 4,176
  • 7
  • 47
  • 81
Damien C
  • 969
  • 1
  • 10
  • 16
0

Same answer as "Damien C" but updated for Hibernate v5.4 (their solution doesn't work anymore with Hibernate v5.4):

import java.io.Serializable;
import java.sql.Array;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.CustomType;
import org.hibernate.type.Type;
import org.hibernate.usertype.UserType;

public class ArrayUserType implements UserType {

    protected static final int[] SQL_TYPES = { Types.ARRAY };


    public static Type createCustomType() {
        return new CustomType(new ArrayUserType());
    }

    /**
     * Return the SQL type codes for the columns mapped by this type. The
     * codes are defined on <tt>java.sql.Types</tt>.
     * 
     * @return int[] typecodes
     * @see java.sql.Types
     */
    public final int[] sqlTypes() {
        return SQL_TYPES;
    }

    /**
     * The class returned by <tt>nullSafeGet()</tt>.
     * 
     * @return Class
     */
    public final Class<?> returnedClass() {
        return String[].class;
    }

    /**
     * Retrieve an instance of the mapped class from a JDBC resultset. Implementors
     * should handle possibility of null values.
     * 
     * @param resultSet A JDBC result set
     * @param names Column names
     * @param session  Session in progress
     * @param owner The containing entity 
     * @return Object
     * @throws java.sql.SQLException If the data can't be fetched
     */
    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor session, Object owner)
            throws SQLException {
        if (resultSet.wasNull()) {
            return null;
        }

        return resultSet.getArray(names[0]).getArray();
    }

    /**
     * Write an instance of the mapped class to a prepared statement. Implementors
     * should handle possibility of null values. A multi-column type should be written
     * to parameters starting from <tt>index</tt>.
     * 
     * @param statement A JDBC prepared statement
     * @param value Object to write
     * @param index Statement parameter index
     * @param session Session in progress
     * @throws java.sql.SQLException If the data can't be fetched
     */
    @Override
    public final void nullSafeSet(PreparedStatement statement, Object value, int index, SharedSessionContractImplementor session)
            throws SQLException {

        if (value == null) {
            statement.setNull(index, SQL_TYPES[0]);
        }
        else {
            String[] castObject = (String[]) value;
            Array array = session.connection().createArrayOf("text", castObject);
            statement.setArray(index, array);
        }
    }

    @Override
    public final Object deepCopy(final Object value) {
        return value;
    }

    @Override
    public final boolean isMutable() {
        return false;
    }

    @Override
    public final Object assemble(final Serializable arg0, final Object arg1) {
        return null;
    }

    @Override
    public final Serializable disassemble(final Object arg0) {
        return null;
    }

    @Override
    public final boolean equals(final Object x, final Object y) {
        if (x == y) {
            return true;
        }
        else if (x == null || y == null) {
            return false;
        }
        else {
            return x.equals(y);
        }
    }

    @Override
    public final int hashCode(final Object x) {
        return x.hashCode();
    }

    @Override
    public final Object replace(final Object original, final Object target, final Object owner) {
        return original;
    }
}

And to use it:

String[] values = ...
query.setParameter("myArray", values, ArrayUserType.createCustomType());
xav
  • 5,452
  • 7
  • 48
  • 57