Hibernate - невозможно выполнить запрос с типом UserType в разделе where

У меня есть Hibernate UserType, определенный для преобразования данных, прежде чем он войдет в нашу базу данных, а затем не преобразует его при чтении из db. Это хорошо работает, когда я вставляю строку или получаю строки с использованием идентификатора строки или другого способа запроса строки. Однако, когда я пытаюсь использовать запрос для поиска записи, привязка параметров кажется неудачной:

org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [thisIsTheSearchString] did not match expected type [com.xxx.MyUserType (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [thisIsTheSearchString] did not match expected type [com.xxx.MyUserType (n/a)]

Я попытался реализовать метод LiteralType и objectToSQLString, но он не выглядит так, как этот метод когда-либо называется.

В качестве упрощенного примера:

public class MyUserType implements UserType, LiteralType {

    @Override
    public int[] sqlTypes() {
        return new int[] {
                Types.VARCHAR
        };
    }

    @Override
    public Class returnedClass() {
        return MyUserType.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return ObjectUtils.equals(x, y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        assert (x != null);
        return x.hashCode();
    }

    @Override
    public Object nullSafeGet(
            ResultSet rs, 
            String[] names, 
            SessionImplementor session, 
            Object owner) 
                    throws HibernateException, SQLException
    {
        assert names.length == 1;
        return untransform( rs.getString( names[0] ); );
    }

    String transform(String untransformed) {
        //...
    }

    String untransform(String transformed) {
        //...
    }

    @Override
    public void nullSafeSet(
            PreparedStatement st, 
            Object value, 
            int index,
            SessionImplementor session)
                    throws HibernateException, SQLException 
    {
        if ( value == null ) {
            st.setNull(index, Types.VARCHAR);
        } else {
            final String untransformed = (String)value;

            return transform(untransformed);
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        if ( value == null ) {
            return null;
        }
        return (String)value;
    }

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

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

    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return deepCopy(cached);
    }

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

    // THIS NEVER GETS CALLED
    @Override
    public String objectToSQLString(Object value, Dialect dialect)
            throws Exception 
    {
        if ( value == null ) {
            return null;
        }

        String transformed = transform((String)value);

        StringType stringType = new StringType();
        String sqlString = stringType.objectToSQLString(transformed, dialect);

        return sqlString;
    }
}

Объект выглядит так:

@Entity
@Table(name = "blah_blah")
@TypeDefs(value = { @TypeDef(name = "transformedText", typeClass = MyUserType.class)})
public class BlahBlah implements Serializable, Persistable<Long> {

    //...

    @Column(name = "transformed")
    @Type(type = "transformedText")
    String transformed;

    //...
}

Мой запрос:

@Query(value = 
        "select b " +
        "from BlahBlah b " +
        "where b.transformed = ?1 ")
public List<BlahBlah> findTransformed(String text);

Ответ 1

Мне кажется, вам нужно изменить возвращаемый класс:

@Override
public Class returnedClass() {
    return MyUserType.class;
}

должен быть:

@Override
public Class returnedClass() {
    return String.class;
}

В документах (https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/usertype/UserType.html#returnedClass()):

returnedClass

Класс returnClass()

Класс, возвращаемый nullSafeGet().   Возвращает:   Класс

и ваш nullSafeGet, похоже, возвращает строку.

Ответ 2

С помощью Spring Data вы можете реализовать пользовательскую реализацию запроса, и вы можете распаковать EntityManager на сеанс Hibernate, а затем предоставить Пользовательский тип, созданный по вашему запросу:

@PersistenceContext
private EntityManager entityManager;

public List<BlahBlah> findTransformed(String text) {
    Session session = entityManager.unwrap(Session.class);

    TypeHelper typeHelper = session.getSessionFactory().getTypeHelper();

    List<BlahBlah> result = (List<BlahBlah>) session.createQuery(
        "select b " +
        "from BlahBlah b " +
        "where b.transformed = :transformed ")
    .setParameter("transformed", text, typeHelper.custom(MyUserType.class))
    .list();
}